From d80f94387a61ae22cda5201192ff87846858c5f8 Mon Sep 17 00:00:00 2001 From: Xiaoxia Date: Tue, 27 May 2025 14:58:49 +0800 Subject: [PATCH] v1.6.6: Set MCP as default IoT Protocol (#690) --- CMakeLists.txt | 2 +- main/Kconfig.projbuild | 6 +- main/application.cc | 48 ++++- main/application.h | 15 +- main/assets/en-US/language.json | 5 +- main/assets/ja-JP/language.json | 5 +- main/assets/zh-CN/language.json | 5 +- main/assets/zh-TW/language.json | 5 +- main/audio_processing/afe_audio_processor.cc | 39 ++-- main/audio_processing/afe_audio_processor.h | 1 + main/audio_processing/audio_processor.h | 1 + .../audio_processing/dummy_audio_processor.cc | 8 +- main/audio_processing/dummy_audio_processor.h | 1 + main/boards/bread-compact-esp32/config.h | 4 + .../bread-compact-esp32/esp32_bread_board.cc | 6 + .../compact_ml307_board.cc | 6 + main/boards/bread-compact-ml307/config.h | 3 + .../compact_wifi_board_lcd.cc | 6 + main/boards/bread-compact-wifi-lcd/config.h | 4 + .../bread-compact-wifi/compact_wifi_board.cc | 8 +- main/boards/bread-compact-wifi/config.h | 4 + main/boards/common/lamp_controller.h | 44 +++++ main/boards/common/wifi_board.cc | 11 ++ main/boards/esp-box-3/esp_box3_board.cc | 9 + main/boards/esp-box/esp_box_board.cc | 9 + main/boards/esp-sparkbot/chassis.cc | 98 ---------- .../boards/esp-sparkbot/esp_sparkbot_board.cc | 98 +++++++++- .../board_control.cc | 32 --- .../esp32-s3-touch-amoled-1.8.cc | 21 +- .../esp32-s3-touch-lcd-3.5/board_control.cc | 31 --- .../esp32-s3-touch-lcd-3.5.cc | 22 ++- .../esp32s3_korvo2_v3_board.cc | 9 + .../genjutech-s3-1.54tft.cc | 1 - main/boards/kevin-c3/kevin_c3_board.cc | 7 +- main/boards/kevin-c3/led_strip_control.cc | 183 +++++++++--------- main/boards/kevin-c3/led_strip_control.h | 5 +- .../boards/lichuang-dev/lichuang_dev_board.cc | 9 + main/boards/minsi-k08-dual/config.h | 4 + main/boards/minsi-k08-dual/minsi_k08_dual.cc | 6 + .../board_control.cc | 32 --- .../esp32-s3-touch-amoled-1.75.cc | 33 +++- main/boards/xmini-c3/xmini_c3_board.cc | 59 +++--- .../zhengchen-1.54tft-wifi/temperature.cc | 29 --- .../zhengchen-1.54tft-wifi.cc | 2 + main/mcp_server.cc | 14 +- main/mcp_server.h | 2 +- 46 files changed, 524 insertions(+), 428 deletions(-) create mode 100644 main/boards/common/lamp_controller.h delete mode 100644 main/boards/esp-sparkbot/chassis.cc delete mode 100644 main/boards/esp32-s3-touch-amoled-1.8/board_control.cc delete mode 100644 main/boards/esp32-s3-touch-lcd-3.5/board_control.cc delete mode 100644 main/boards/waveshare-s3-touch-amoled-1.75/board_control.cc delete mode 100644 main/boards/zhengchen-1.54tft-wifi/temperature.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ebcd8f6..434e2328 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.6.5") +set(PROJECT_VER "1.6.6") # Add this line to disable the specific warning add_compile_options(-Wno-missing-field-initializers) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 686bcf3b..b36f4aaf 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -158,10 +158,10 @@ choice BOARD_TYPE bool "乐鑫ESP S3 LCD EV Board开发板" config BOARD_TYPE_ZHENGCHEN_1_54TFT_WIFI bool "征辰科技1.54(WIFI)" - config BOARD_TYPE_MINSI_K08_DUAL - bool "敏思科技K08(DUAL)" config BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307 bool "征辰科技1.54(ML307)" + config BOARD_TYPE_MINSI_K08_DUAL + bool "敏思科技K08(DUAL)" config BOARD_TYPE_ESP32_S3_1_54_MUMA bool "Spotpear ESP32-S3-1.54-MUMA" endchoice @@ -284,7 +284,7 @@ config USE_SERVER_AEC choice IOT_PROTOCOL prompt "IoT Protocol" - default IOT_PROTOCOL_XIAOZHI + default IOT_PROTOCOL_MCP help IoT 协议,用于获取设备状态与发送控制指令 config IOT_PROTOCOL_MCP diff --git a/main/application.cc b/main/application.cc index 61ab7f20..71753835 100644 --- a/main/application.cc +++ b/main/application.cc @@ -44,6 +44,14 @@ Application::Application() { event_group_ = xEventGroupCreate(); background_task_ = new BackgroundTask(4096 * 7); +#if CONFIG_USE_DEVICE_AEC + aec_mode_ = kAecOnDeviceSide; +#elif CONFIG_USE_SERVER_AEC + aec_mode_ = kAecOnServerSide; +#else + aec_mode_ = kAecOff; +#endif + #if CONFIG_USE_AUDIO_PROCESSOR audio_processor_ = std::make_unique(); #else @@ -285,7 +293,7 @@ void Application::ToggleChatState() { return; } - SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeAutoStop); + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); }); } else if (device_state_ == kDeviceStateSpeaking) { Schedule([this]() { @@ -358,8 +366,8 @@ void Application::Start() { auto codec = board.GetAudioCodec(); opus_decoder_ = std::make_unique(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS); opus_encoder_ = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); - if (realtime_chat_enabled_) { - ESP_LOGI(TAG, "Realtime chat enabled, setting opus encoder complexity to 0"); + if (aec_mode_ != kAecOff) { + ESP_LOGI(TAG, "AEC mode: %d, setting opus encoder complexity to 0", aec_mode_); opus_encoder_->SetComplexity(0); } else if (board.GetBoardType() == "ml307") { ESP_LOGI(TAG, "ML307 board detected, setting opus encoder complexity to 5"); @@ -404,6 +412,11 @@ void Application::Start() { // Initialize the protocol display->SetStatus(Lang::Strings::LOADING_PROTOCOL); + // Add MCP common tools before initializing the protocol +#if CONFIG_IOT_PROTOCOL_MCP + McpServer::GetInstance().AddCommonTools(); +#endif + if (ota_.HasMqttConfig()) { protocol_ = std::make_unique(); } else if (ota_.HasWebsocketConfig()) { @@ -608,7 +621,7 @@ void Application::Start() { // Set the chat state to wake word detected protocol_->SendWakeWordDetected(wake_word); ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str()); - SetListeningMode(realtime_chat_enabled_ ? kListeningModeRealtime : kListeningModeAutoStop); + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); } else if (device_state_ == kDeviceStateSpeaking) { AbortSpeaking(kAbortReasonWakeWordDetected); } else if (device_state_ == kDeviceStateActivating) { @@ -1002,3 +1015,30 @@ void Application::SendMcpMessage(const std::string& payload) { } }); } + +void Application::SetAecMode(AecMode mode) { + aec_mode_ = mode; + Schedule([this]() { + auto& board = Board::GetInstance(); + auto display = board.GetDisplay(); + switch (aec_mode_) { + case kAecOff: + audio_processor_->EnableDeviceAec(false); + display->ShowNotification(Lang::Strings::RTC_MODE_OFF); + break; + case kAecOnServerSide: + audio_processor_->EnableDeviceAec(false); + display->ShowNotification(Lang::Strings::RTC_MODE_ON); + break; + case kAecOnDeviceSide: + audio_processor_->EnableDeviceAec(true); + display->ShowNotification(Lang::Strings::RTC_MODE_ON); + break; + } + + // If the AEC mode is changed, close the audio channel + if (protocol_ && protocol_->IsAudioChannelOpened()) { + protocol_->CloseAudioChannel(); + } + }); +} diff --git a/main/application.h b/main/application.h index 66a6f930..df182e31 100644 --- a/main/application.h +++ b/main/application.h @@ -30,6 +30,12 @@ #define SEND_AUDIO_EVENT (1 << 1) #define CHECK_NEW_VERSION_DONE_EVENT (1 << 2) +enum AecMode { + kAecOff, + kAecOnDeviceSide, + kAecOnServerSide, +}; + enum DeviceState { kDeviceStateUnknown, kDeviceStateStarting, @@ -73,6 +79,8 @@ public: void PlaySound(const std::string_view& sound); bool CanEnterSleepMode(); void SendMcpMessage(const std::string& payload); + void SetAecMode(AecMode mode); + AecMode GetAecMode() const { return aec_mode_; } private: Application(); @@ -90,11 +98,8 @@ private: esp_timer_handle_t clock_timer_handle_ = nullptr; volatile DeviceState device_state_ = kDeviceStateUnknown; ListeningMode listening_mode_ = kListeningModeAutoStop; -#if CONFIG_USE_DEVICE_AEC || CONFIG_USE_SERVER_AEC - bool realtime_chat_enabled_ = true; -#else - bool realtime_chat_enabled_ = false; -#endif + AecMode aec_mode_ = kAecOff; + bool aborted_ = false; bool voice_detected_ = false; bool busy_decoding_audio_ = false; diff --git a/main/assets/en-US/language.json b/main/assets/en-US/language.json index 3af0d09d..54ffb0a0 100644 --- a/main/assets/en-US/language.json +++ b/main/assets/en-US/language.json @@ -51,6 +51,9 @@ "VOLUME": "Volume ", "MUTED": "Muted", - "MAX_VOLUME": "Max volume" + "MAX_VOLUME": "Max volume", + + "RTC_MODE_OFF": "AEC Off", + "RTC_MODE_ON": "AEC On" } } \ No newline at end of file diff --git a/main/assets/ja-JP/language.json b/main/assets/ja-JP/language.json index fb05904b..7cf00cd2 100644 --- a/main/assets/ja-JP/language.json +++ b/main/assets/ja-JP/language.json @@ -50,6 +50,9 @@ "VOLUME": "音量 ", "MUTED": "ミュートされています", - "MAX_VOLUME": "最大音量" + "MAX_VOLUME": "最大音量", + + "RTC_MODE_OFF": "AEC 無効", + "RTC_MODE_ON": "AEC 有効" } } diff --git a/main/assets/zh-CN/language.json b/main/assets/zh-CN/language.json index 9eb47b95..eeb61b4c 100644 --- a/main/assets/zh-CN/language.json +++ b/main/assets/zh-CN/language.json @@ -50,6 +50,9 @@ "VOLUME":"音量 ", "MUTED":"已静音", - "MAX_VOLUME":"最大音量" + "MAX_VOLUME":"最大音量", + + "RTC_MODE_OFF":"AEC 关闭", + "RTC_MODE_ON":"AEC 开启" } } diff --git a/main/assets/zh-TW/language.json b/main/assets/zh-TW/language.json index d61cfc79..51175a1c 100644 --- a/main/assets/zh-TW/language.json +++ b/main/assets/zh-TW/language.json @@ -50,6 +50,9 @@ "VOLUME": "音量 ", "MUTED": "已靜音", - "MAX_VOLUME": "最大音量" + "MAX_VOLUME": "最大音量", + + "RTC_MODE_OFF": "AEC 關閉", + "RTC_MODE_ON": "AEC 開啟" } } diff --git a/main/audio_processing/afe_audio_processor.cc b/main/audio_processing/afe_audio_processor.cc index fa931d80..3415ff59 100644 --- a/main/audio_processing/afe_audio_processor.cc +++ b/main/audio_processing/afe_audio_processor.cc @@ -26,27 +26,26 @@ void AfeAudioProcessor::Initialize(AudioCodec* codec) { char* ns_model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL); afe_config_t* afe_config = afe_config_init(input_format.c_str(), NULL, AFE_TYPE_VC, AFE_MODE_HIGH_PERF); -#ifdef CONFIG_USE_DEVICE_AEC - afe_config->aec_init = true; afe_config->aec_mode = AEC_MODE_VOIP_HIGH_PERF; -#else - afe_config->aec_init = false; -#endif + afe_config->vad_mode = VAD_MODE_0; + afe_config->vad_min_noise_ms = 100; afe_config->ns_init = true; afe_config->ns_model_name = ns_model_name; afe_config->afe_ns_mode = AFE_NS_MODE_NET; -#ifdef CONFIG_USE_DEVICE_AEC - afe_config->vad_init = false; -#else - afe_config->vad_init = true; - afe_config->vad_mode = VAD_MODE_0; - afe_config->vad_min_noise_ms = 100; -#endif + afe_config->afe_perferred_core = 1; afe_config->afe_perferred_priority = 1; afe_config->agc_init = false; afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM; +#ifdef CONFIG_USE_DEVICE_AEC + afe_config->aec_init = true; + afe_config->vad_init = false; +#else + afe_config->aec_init = false; + afe_config->vad_init = true; +#endif + afe_iface_ = esp_afe_handle_from_config(afe_config); afe_data_ = afe_iface_->create_from_config(afe_config); @@ -136,4 +135,18 @@ void AfeAudioProcessor::AudioProcessorTask() { output_callback_(std::vector(res->data, res->data + res->data_size / sizeof(int16_t))); } } -} \ No newline at end of file +} + +void AfeAudioProcessor::EnableDeviceAec(bool enable) { + if (enable) { +#if CONFIG_USE_DEVICE_AEC + afe_iface_->disable_vad(afe_data_); + afe_iface_->enable_aec(afe_data_); +#else + ESP_LOGE(TAG, "Device AEC is not supported"); +#endif + } else { + afe_iface_->disable_aec(afe_data_); + afe_iface_->enable_vad(afe_data_); + } +} diff --git a/main/audio_processing/afe_audio_processor.h b/main/audio_processing/afe_audio_processor.h index 9a9cfdaf..04bfcbe7 100644 --- a/main/audio_processing/afe_audio_processor.h +++ b/main/audio_processing/afe_audio_processor.h @@ -26,6 +26,7 @@ public: void OnOutput(std::function&& data)> callback) override; void OnVadStateChange(std::function callback) override; size_t GetFeedSize() override; + void EnableDeviceAec(bool enable) override; private: EventGroupHandle_t event_group_ = nullptr; diff --git a/main/audio_processing/audio_processor.h b/main/audio_processing/audio_processor.h index 5acff47f..3c729e06 100644 --- a/main/audio_processing/audio_processor.h +++ b/main/audio_processing/audio_processor.h @@ -19,6 +19,7 @@ public: virtual void OnOutput(std::function&& data)> callback) = 0; virtual void OnVadStateChange(std::function callback) = 0; virtual size_t GetFeedSize() = 0; + virtual void EnableDeviceAec(bool enable) = 0; }; #endif diff --git a/main/audio_processing/dummy_audio_processor.cc b/main/audio_processing/dummy_audio_processor.cc index 95935af5..7cb606d7 100644 --- a/main/audio_processing/dummy_audio_processor.cc +++ b/main/audio_processing/dummy_audio_processor.cc @@ -41,4 +41,10 @@ size_t DummyAudioProcessor::GetFeedSize() { } // 返回一个固定的帧大小,比如 30ms 的数据 return 30 * codec_->input_sample_rate() / 1000; -} \ No newline at end of file +} + +void DummyAudioProcessor::EnableDeviceAec(bool enable) { + if (enable) { + ESP_LOGE(TAG, "Device AEC is not supported"); + } +} diff --git a/main/audio_processing/dummy_audio_processor.h b/main/audio_processing/dummy_audio_processor.h index 29377974..4383b7a9 100644 --- a/main/audio_processing/dummy_audio_processor.h +++ b/main/audio_processing/dummy_audio_processor.h @@ -20,6 +20,7 @@ public: void OnOutput(std::function&& data)> callback) override; void OnVadStateChange(std::function callback) override; size_t GetFeedSize() override; + void EnableDeviceAec(bool enable) override; private: AudioCodec* codec_ = nullptr; diff --git a/main/boards/bread-compact-esp32/config.h b/main/boards/bread-compact-esp32/config.h index 177e866c..3ff28ea8 100644 --- a/main/boards/bread-compact-esp32/config.h +++ b/main/boards/bread-compact-esp32/config.h @@ -48,4 +48,8 @@ #define DISPLAY_MIRROR_X true #define DISPLAY_MIRROR_Y true + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-esp32/esp32_bread_board.cc b/main/boards/bread-compact-esp32/esp32_bread_board.cc index 148969de..45e8a102 100644 --- a/main/boards/bread-compact-esp32/esp32_bread_board.cc +++ b/main/boards/bread-compact-esp32/esp32_bread_board.cc @@ -4,6 +4,8 @@ #include "application.h" #include "button.h" #include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" #include "iot/thing_manager.h" #include "led/single_led.h" #include "display/oled_display.h" @@ -133,9 +135,13 @@ private: // 物联网初始化,添加对 AI 可见设备 void InitializeIot() { +#if CONFIG_IOT_PROTOCOL_XIAOZHI auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Lamp")); +#elif CONFIG_IOT_PROTOCOL_MCP + static LampController lamp(LAMP_GPIO); +#endif } public: diff --git a/main/boards/bread-compact-ml307/compact_ml307_board.cc b/main/boards/bread-compact-ml307/compact_ml307_board.cc index f42d265e..922d1620 100644 --- a/main/boards/bread-compact-ml307/compact_ml307_board.cc +++ b/main/boards/bread-compact-ml307/compact_ml307_board.cc @@ -5,6 +5,8 @@ #include "application.h" #include "button.h" #include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" #include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" @@ -154,9 +156,13 @@ private: // 物联网初始化,添加对 AI 可见设备 void InitializeIot() { +#if CONFIG_IOT_PROTOCOL_XIAOZHI auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Lamp")); +#elif CONFIG_IOT_PROTOCOL_MCP + static LampController lamp(LAMP_GPIO); +#endif } public: diff --git a/main/boards/bread-compact-ml307/config.h b/main/boards/bread-compact-ml307/config.h index 53db9c24..de9ef7f2 100644 --- a/main/boards/bread-compact-ml307/config.h +++ b/main/boards/bread-compact-ml307/config.h @@ -53,4 +53,7 @@ #define ML307_TX_PIN GPIO_NUM_12 +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc index 43a8615b..a5c8b89a 100644 --- a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc +++ b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc @@ -5,6 +5,8 @@ #include "application.h" #include "button.h" #include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" #include "iot/thing_manager.h" #include "led/single_led.h" @@ -149,10 +151,14 @@ private: // 物联网初始化,添加对 AI 可见设备 void InitializeIot() { +#if CONFIG_IOT_PROTOCOL_XIAOZHI auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Screen")); thing_manager.AddThing(iot::CreateThing("Lamp")); +#elif CONFIG_IOT_PROTOCOL_MCP + static LampController lamp(LAMP_GPIO); +#endif } public: diff --git a/main/boards/bread-compact-wifi-lcd/config.h b/main/boards/bread-compact-wifi-lcd/config.h index 0c7c3466..12d3802a 100644 --- a/main/boards/bread-compact-wifi-lcd/config.h +++ b/main/boards/bread-compact-wifi-lcd/config.h @@ -282,4 +282,8 @@ #define DISPLAY_SPI_MODE 0 #endif + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index cfdc1afa..3608a754 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -5,6 +5,8 @@ #include "application.h" #include "button.h" #include "config.h" +#include "mcp_server.h" +#include "lamp_controller.h" #include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" @@ -150,11 +152,15 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 + // 物联网初始化,逐步迁移到 MCP 协议 void InitializeIot() { +#if CONFIG_IOT_PROTOCOL_XIAOZHI auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Lamp")); +#elif CONFIG_IOT_PROTOCOL_MCP + static LampController lamp(LAMP_GPIO); +#endif } public: diff --git a/main/boards/bread-compact-wifi/config.h b/main/boards/bread-compact-wifi/config.h index f0e2724f..7bb0982b 100644 --- a/main/boards/bread-compact-wifi/config.h +++ b/main/boards/bread-compact-wifi/config.h @@ -52,4 +52,8 @@ #define DISPLAY_MIRROR_X true #define DISPLAY_MIRROR_Y true + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/common/lamp_controller.h b/main/boards/common/lamp_controller.h new file mode 100644 index 00000000..1ed142c7 --- /dev/null +++ b/main/boards/common/lamp_controller.h @@ -0,0 +1,44 @@ +#ifndef __LAMP_CONTROLLER_H__ +#define __LAMP_CONTROLLER_H__ + +#include "mcp_server.h" + + +class LampController { +private: + bool power_ = false; + gpio_num_t gpio_num_; + +public: + LampController(gpio_num_t gpio_num) : gpio_num_(gpio_num) { + gpio_config_t config = { + .pin_bit_mask = (1ULL << gpio_num_), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&config)); + gpio_set_level(gpio_num_, 0); + + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.lamp.get_state", "Get the power state of the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return power_ ? "{\"power\": true}" : "{\"power\": false}"; + }); + + mcp_server.AddTool("self.lamp.turn_on", "Turn on the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + power_ = true; + gpio_set_level(gpio_num_, 1); + return true; + }); + + mcp_server.AddTool("self.lamp.turn_off", "Turn off the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + power_ = false; + gpio_set_level(gpio_num_, 0); + return true; + }); + } +}; + + +#endif // __LAMP_CONTROLLER_H__ diff --git a/main/boards/common/wifi_board.cc b/main/boards/common/wifi_board.cc index 494e2c46..25853bae 100644 --- a/main/boards/common/wifi_board.cc +++ b/main/boards/common/wifi_board.cc @@ -203,6 +203,9 @@ std::string WifiBoard::GetDeviceStatusJson() { * "type": "wifi", * "ssid": "Xiaozhi", * "rssi": -60 + * }, + * "chip": { + * "temperature": 25 * } * } */ @@ -255,6 +258,14 @@ std::string WifiBoard::GetDeviceStatusJson() { } cJSON_AddItemToObject(root, "network", network); + // Chip + float esp32temp = 0.0f; + if (board.GetTemperature(esp32temp)) { + auto chip = cJSON_CreateObject(); + cJSON_AddNumberToObject(chip, "temperature", esp32temp); + cJSON_AddItemToObject(root, "chip", chip); + } + auto json_str = cJSON_PrintUnformatted(root); std::string json(json_str); cJSON_free(json_str); diff --git a/main/boards/esp-box-3/esp_box3_board.cc b/main/boards/esp-box-3/esp_box3_board.cc index b8d9eda1..1bf77a8e 100644 --- a/main/boards/esp-box-3/esp_box3_board.cc +++ b/main/boards/esp-box-3/esp_box3_board.cc @@ -82,6 +82,15 @@ private: } app.ToggleChatState(); }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif } void InitializeIli9341Display() { diff --git a/main/boards/esp-box/esp_box_board.cc b/main/boards/esp-box/esp_box_board.cc index 079b7961..c2e14b94 100644 --- a/main/boards/esp-box/esp_box_board.cc +++ b/main/boards/esp-box/esp_box_board.cc @@ -82,6 +82,15 @@ private: } app.ToggleChatState(); }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif } void InitializeIli9341Display() { diff --git a/main/boards/esp-sparkbot/chassis.cc b/main/boards/esp-sparkbot/chassis.cc deleted file mode 100644 index d970ad88..00000000 --- a/main/boards/esp-sparkbot/chassis.cc +++ /dev/null @@ -1,98 +0,0 @@ -/* - ESP-SparkBot 的底座 - https://gitee.com/esp-friends/esp_sparkbot/tree/master/example/tank/c2_tracked_chassis -*/ - -#include "sdkconfig.h" -#include "iot/thing.h" -#include "board.h" - -#include -#include -#include -#include - -#include "boards/esp-sparkbot/config.h" - -#define TAG "Chassis" - -namespace iot { - -class Chassis : public Thing { -private: - light_mode_t light_mode_ = LIGHT_MODE_ALWAYS_ON; - - void SendUartMessage(const char * command_str) { - uint8_t len = strlen(command_str); - uart_write_bytes(ECHO_UART_PORT_NUM, command_str, len); - ESP_LOGI(TAG, "Sent command: %s", command_str); - } - - void InitializeEchoUart() { - uart_config_t uart_config = { - .baud_rate = ECHO_UART_BAUD_RATE, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - .source_clk = UART_SCLK_DEFAULT, - }; - int intr_alloc_flags = 0; - - ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags)); - ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config)); - ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, UART_ECHO_TXD, UART_ECHO_RXD, UART_ECHO_RTS, UART_ECHO_CTS)); - - SendUartMessage("w2"); - } - -public: - Chassis() : Thing("Chassis", "小机器人的底座:有履带可以移动;可以调整灯光效果"), light_mode_(LIGHT_MODE_ALWAYS_ON) { - InitializeEchoUart(); - - // 定义设备的属性 - properties_.AddNumberProperty("light_mode", "灯光效果编号", [this]() -> int { - return (light_mode_ - 2 <= 0) ? 1 : light_mode_ - 2; - }); - - // 定义设备可以被远程执行的指令 - methods_.AddMethod("GoForward", "向前走", ParameterList(), [this](const ParameterList& parameters) { - SendUartMessage("x0.0 y1.0"); - }); - - methods_.AddMethod("GoBack", "向后退", ParameterList(), [this](const ParameterList& parameters) { - SendUartMessage("x0.0 y-1.0"); - }); - - methods_.AddMethod("TurnLeft", "向左转", ParameterList(), [this](const ParameterList& parameters) { - SendUartMessage("x-1.0 y0.0"); - }); - - methods_.AddMethod("TurnRight", "向右转", ParameterList(), [this](const ParameterList& parameters) { - SendUartMessage("x1.0 y0.0"); - }); - - methods_.AddMethod("Dance", "跳舞", ParameterList(), [this](const ParameterList& parameters) { - SendUartMessage("d1"); - light_mode_ = LIGHT_MODE_MAX; - }); - - methods_.AddMethod("SwitchLightMode", "打开灯", ParameterList({ - Parameter("lightmode", "1到6之间的整数", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - char command_str[5] = {'w', 0, 0}; - char mode = static_cast(parameters["lightmode"].number()) + 2; - - ESP_LOGI(TAG, "Input Light Mode: %c", (mode + '0')); - - if (mode >= 3 && mode <= 8) { - command_str[1] = mode + '0'; - SendUartMessage(command_str); - } - }); - } -}; - -} // namespace iot - -DECLARE_THING(Chassis); diff --git a/main/boards/esp-sparkbot/esp_sparkbot_board.cc b/main/boards/esp-sparkbot/esp_sparkbot_board.cc index 90614045..0d773585 100644 --- a/main/boards/esp-sparkbot/esp_sparkbot_board.cc +++ b/main/boards/esp-sparkbot/esp_sparkbot_board.cc @@ -5,13 +5,15 @@ #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" +#include "mcp_server.h" #include #include #include #include #include +#include +#include #include "esp32_camera.h" @@ -48,6 +50,7 @@ private: Button boot_button_; Display* display_; Esp32Camera* camera_; + light_mode_t light_mode_ = LIGHT_MODE_ALWAYS_ON; void InitializeI2c() { // Initialize I2C peripheral @@ -132,8 +135,8 @@ private: camera_config.pin_reset = SPARKBOT_CAMERA_RESET; camera_config.pin_xclk = SPARKBOT_CAMERA_XCLK; camera_config.pin_pclk = SPARKBOT_CAMERA_PCLK; - camera_config.pin_sscb_sda = SPARKBOT_CAMERA_SIOD; - camera_config.pin_sscb_scl = SPARKBOT_CAMERA_SIOC; + camera_config.pin_sccb_sda = SPARKBOT_CAMERA_SIOD; + camera_config.pin_sccb_scl = SPARKBOT_CAMERA_SIOC; camera_config.pin_d0 = SPARKBOT_CAMERA_D0; camera_config.pin_d1 = SPARKBOT_CAMERA_D1; @@ -163,12 +166,86 @@ private: camera_ = new Esp32Camera(camera_config); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Chassis")); + /* + ESP-SparkBot 的底座 + https://gitee.com/esp-friends/esp_sparkbot/tree/master/example/tank/c2_tracked_chassis + */ + void InitializeEchoUart() { + uart_config_t uart_config = { + .baud_rate = ECHO_UART_BAUD_RATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT, + }; + int intr_alloc_flags = 0; + + ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags)); + ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config)); + ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, UART_ECHO_TXD, UART_ECHO_RXD, UART_ECHO_RTS, UART_ECHO_CTS)); + + SendUartMessage("w2"); + } + + void SendUartMessage(const char * command_str) { + uint8_t len = strlen(command_str); + uart_write_bytes(ECHO_UART_PORT_NUM, command_str, len); + ESP_LOGI(TAG, "Sent command: %s", command_str); + } + + void InitializeTools() { + auto& mcp_server = McpServer::GetInstance(); + // 定义设备的属性 + mcp_server.AddTool("self.chassis.get_light_mode", "获取灯光效果编号", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + if (light_mode_ < 2) { + return 1; + } else { + return light_mode_ - 2; + } + }); + + mcp_server.AddTool("self.chassis.go_forward", "前进", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("x0.0 y1.0"); + return true; + }); + + mcp_server.AddTool("self.chassis.go_back", "后退", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("x0.0 y-1.0"); + return true; + }); + + mcp_server.AddTool("self.chassis.turn_left", "向左转", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("x-1.0 y0.0"); + return true; + }); + + mcp_server.AddTool("self.chassis.turn_right", "向右转", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("x1.0 y0.0"); + return true; + }); + + mcp_server.AddTool("self.chassis.dance", "跳舞", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SendUartMessage("d1"); + light_mode_ = LIGHT_MODE_MAX; + return true; + }); + + mcp_server.AddTool("self.chassis.switch_light_mode", "打开灯光效果", PropertyList({ + Property("light_mode", kPropertyTypeInteger, 1, 6) + }), [this](const PropertyList& properties) -> ReturnValue { + char command_str[5] = {'w', 0, 0}; + char mode = static_cast(properties["light_mode"].value()); + + ESP_LOGI(TAG, "Switch Light Mode: %c", (mode + '0')); + + if (mode >= 3 && mode <= 8) { + command_str[1] = mode + '0'; + SendUartMessage(command_str); + return true; + } + throw std::runtime_error("Invalid light mode"); + }); } public: @@ -177,8 +254,9 @@ public: InitializeSpi(); InitializeDisplay(); InitializeButtons(); - InitializeIot(); InitializeCamera(); + InitializeEchoUart(); + InitializeTools(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp32-s3-touch-amoled-1.8/board_control.cc b/main/boards/esp32-s3-touch-amoled-1.8/board_control.cc deleted file mode 100644 index b3f2163e..00000000 --- a/main/boards/esp32-s3-touch-amoled-1.8/board_control.cc +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include -#include -#include - -#include "board.h" -#include "boards/common/wifi_board.h" -#include "boards/esp32-s3-touch-amoled-1.8/config.h" -#include "iot/thing.h" - -#define TAG "BoardControl" - -namespace iot { - -class BoardControl : public Thing { -public: - BoardControl() : Thing("BoardControl", "当前 AI 机器人管理和控制") { - // 修改重新配网 - methods_.AddMethod("ResetWifiConfiguration", "重新配网", ParameterList(), - [this](const ParameterList& parameters) { - ESP_LOGI(TAG, "ResetWifiConfiguration"); - auto board = static_cast(&Board::GetInstance()); - if (board && board->GetBoardType() == "wifi") { - board->ResetWifiConfiguration(); - } - }); - } -}; - -} // namespace iot - -DECLARE_THING(BoardControl); diff --git a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc index d7a13970..73a4c23c 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc +++ b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc @@ -7,7 +7,7 @@ #include "application.h" #include "button.h" #include "led/single_led.h" -#include "iot/thing_manager.h" +#include "mcp_server.h" #include "config.h" #include "power_save_timer.h" #include "axp2101.h" @@ -288,13 +288,16 @@ private: ESP_LOGI(TAG, "Touch panel initialized successfully"); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - thing_manager.AddThing(iot::CreateThing("BoardControl")); + // 初始化工具 + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "Reboot the device and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + ResetWifiConfiguration(); + return true; + }); } public: @@ -308,7 +311,7 @@ public: InitializeSH8601Display(); InitializeTouch(); InitializeButtons(); - InitializeIot(); + InitializeTools(); } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/boards/esp32-s3-touch-lcd-3.5/board_control.cc b/main/boards/esp32-s3-touch-lcd-3.5/board_control.cc deleted file mode 100644 index 2198eab5..00000000 --- a/main/boards/esp32-s3-touch-lcd-3.5/board_control.cc +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include -#include -#include - -#include "board.h" -#include "boards/common/wifi_board.h" -#include "iot/thing.h" - -#define TAG "BoardControl" - -namespace iot { - -class BoardControl : public Thing { -public: - BoardControl() : Thing("BoardControl", "当前 AI 机器人管理和控制") { - // 修改重新配网 - methods_.AddMethod("ResetWifiConfiguration", "重新配网", ParameterList(), - [this](const ParameterList& parameters) { - ESP_LOGI(TAG, "ResetWifiConfiguration"); - auto board = static_cast(&Board::GetInstance()); - if (board && board->GetBoardType() == "wifi") { - board->ResetWifiConfiguration(); - } - }); - } -}; - -} // namespace iot - -DECLARE_THING(BoardControl); diff --git a/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc b/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc index bed51cbb..b7747aee 100644 --- a/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc +++ b/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc @@ -5,8 +5,7 @@ #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" - +#include "mcp_server.h" #include #include "i2c_device.h" @@ -233,13 +232,16 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - thing_manager.AddThing(iot::CreateThing("BoardControl")); + // 初始化工具 + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "Reboot the device and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + ResetWifiConfiguration(); + return true; + }); } public: @@ -257,7 +259,7 @@ public: esp_restart(); } InitializeButtons(); - InitializeIot(); + InitializeTools(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc index e031bd72..e386f319 100644 --- a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc +++ b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc @@ -140,6 +140,15 @@ private: } app.ToggleChatState(); }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif } void InitializeIli9341Display() { diff --git a/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc b/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc index ce4412e6..629a34c1 100644 --- a/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc +++ b/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc @@ -224,7 +224,6 @@ private: auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Screen")); - // thing_manager.AddThing(iot::CreateThing("Lamp")); thing_manager.AddThing(iot::CreateThing("Battery")); } diff --git a/main/boards/kevin-c3/kevin_c3_board.cc b/main/boards/kevin-c3/kevin_c3_board.cc index ab51c60f..3e99650e 100644 --- a/main/boards/kevin-c3/kevin_c3_board.cc +++ b/main/boards/kevin-c3/kevin_c3_board.cc @@ -3,7 +3,6 @@ #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/circular_strip.h" #include "led_strip_control.h" @@ -54,12 +53,8 @@ private: // 物联网初始化,添加对 AI 可见设备 void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 8); - auto led_strip_control = new LedStripControl(led_strip_); - thing_manager.AddThing(led_strip_control); + new LedStripControl(led_strip_); } public: diff --git a/main/boards/kevin-c3/led_strip_control.cc b/main/boards/kevin-c3/led_strip_control.cc index 48634c0f..e0d52786 100644 --- a/main/boards/kevin-c3/led_strip_control.cc +++ b/main/boards/kevin-c3/led_strip_control.cc @@ -1,5 +1,6 @@ #include "led_strip_control.h" #include "settings.h" +#include "mcp_server.h" #include #define TAG "LedStripControl" @@ -22,102 +23,108 @@ StripColor LedStripControl::RGBToColor(int red, int green, int blue) { } LedStripControl::LedStripControl(CircularStrip* led_strip) - : Thing("LedStripControl", "LED 灯带控制,一共有8个灯珠"), led_strip_(led_strip) { + : led_strip_(led_strip) { // 从设置中读取亮度等级 Settings settings("led_strip"); brightness_level_ = settings.GetInt("brightness", 4); // 默认等级4 led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); - // 定义设备的属性 - properties_.AddNumberProperty("brightness", "对话时的亮度等级(0-8)", [this]() -> int { - return brightness_level_; - }); + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.led_strip.get_brightness", + "Get the brightness of the led strip (0-8)", + PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return brightness_level_; + }); - // 定义设备可以被远程执行的指令 - methods_.AddMethod("SetBrightness", "设置对话时的亮度等级", ParameterList({ - Parameter("level", "亮度等级(0-8)", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - int level = static_cast(parameters["level"].number()); - ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level); - - if (level < 0) level = 0; - if (level > 8) level = 8; - - brightness_level_ = level; - led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); - - // 保存设置 - Settings settings("led_strip", true); - settings.SetInt("brightness", brightness_level_); - }); + mcp_server.AddTool("self.led_strip.set_brightness", + "Set the brightness of the led strip (0-8)", + PropertyList({ + Property("level", kPropertyTypeInteger, 0, 8) + }), [this](const PropertyList& properties) -> ReturnValue { + int level = properties["level"].value(); + ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level); + brightness_level_ = level; + led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4); - methods_.AddMethod("SetSingleColor", "设置单个灯颜色", ParameterList({ - Parameter("index", "灯珠索引(0-7)", kValueTypeNumber, true), - Parameter("red", "红色(0-255)", kValueTypeNumber, true), - Parameter("green", "绿色(0-255)", kValueTypeNumber, true), - Parameter("blue", "蓝色(0-255)", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - int index = parameters["index"].number(); - StripColor color = RGBToColor( - parameters["red"].number(), - parameters["green"].number(), - parameters["blue"].number() - ); - ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d", - index, color.red, color.green, color.blue); - led_strip_->SetSingleColor(index, color); - }); + // 保存设置 + Settings settings("led_strip", true); + settings.SetInt("brightness", brightness_level_); - methods_.AddMethod("SetAllColor", "设置所有灯颜色", ParameterList({ - Parameter("red", "红色(0-255)", kValueTypeNumber, true), - Parameter("green", "绿色(0-255)", kValueTypeNumber, true), - Parameter("blue", "蓝色(0-255)", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - StripColor color = RGBToColor( - parameters["red"].number(), - parameters["green"].number(), - parameters["blue"].number() - ); - ESP_LOGI(TAG, "Set led strip color to %d, %d, %d", - color.red, color.green, color.blue - ); - led_strip_->SetAllColor(color); - }); + return true; + }); - methods_.AddMethod("Blink", "闪烁动画", ParameterList({ - Parameter("red", "红色(0-255)", kValueTypeNumber, true), - Parameter("green", "绿色(0-255)", kValueTypeNumber, true), - Parameter("blue", "蓝色(0-255)", kValueTypeNumber, true), - Parameter("interval", "间隔(ms)", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - int interval = parameters["interval"].number(); - StripColor color = RGBToColor( - parameters["red"].number(), - parameters["green"].number(), - parameters["blue"].number() - ); - ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms", - color.red, color.green, color.blue, interval); - led_strip_->Blink(color, interval); - }); + mcp_server.AddTool("self.led_strip.set_single_color", + "Set the color of a single led.", + PropertyList({ + Property("index", kPropertyTypeInteger, 0, 7), + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int index = properties["index"].value(); + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d", + index, red, green, blue); + led_strip_->SetSingleColor(index, RGBToColor(red, green, blue)); + return true; + }); + + mcp_server.AddTool("self.led_strip.set_all_color", + "Set the color of all leds.", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + ESP_LOGI(TAG, "Set led strip all color to %d, %d, %d", + red, green, blue); + led_strip_->SetAllColor(RGBToColor(red, green, blue)); + return true; + }); + + mcp_server.AddTool("self.led_strip.blink", + "Blink the led strip. (闪烁)", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255), + Property("interval", kPropertyTypeInteger, 0, 1000) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + int interval = properties["interval"].value(); + ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms", + red, green, blue, interval); + led_strip_->Blink(RGBToColor(red, green, blue), interval); + return true; + }); + + mcp_server.AddTool("self.led_strip.scroll", + "Scroll the led strip. (跑马灯)", + PropertyList({ + Property("red", kPropertyTypeInteger, 0, 255), + Property("green", kPropertyTypeInteger, 0, 255), + Property("blue", kPropertyTypeInteger, 0, 255), + Property("length", kPropertyTypeInteger, 1, 7), + Property("interval", kPropertyTypeInteger, 0, 1000) + }), [this](const PropertyList& properties) -> ReturnValue { + int red = properties["red"].value(); + int green = properties["green"].value(); + int blue = properties["blue"].value(); + int interval = properties["interval"].value(); + int length = properties["length"].value(); + ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms", + red, green, blue, length, interval); + StripColor low = RGBToColor(4, 4, 4); + StripColor high = RGBToColor(red, green, blue); + led_strip_->Scroll(low, high, length, interval); + return true; + }); - methods_.AddMethod("Scroll", "跑马灯动画", ParameterList({ - Parameter("red", "红色(0-255)", kValueTypeNumber, true), - Parameter("green", "绿色(0-255)", kValueTypeNumber, true), - Parameter("blue", "蓝色(0-255)", kValueTypeNumber, true), - Parameter("length", "滚动条长度(1-7)", kValueTypeNumber, true), - Parameter("interval", "间隔(ms)", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - int interval = parameters["interval"].number(); - int length = parameters["length"].number(); - StripColor low = RGBToColor(4, 4, 4); - StripColor high = RGBToColor( - parameters["red"].number(), - parameters["green"].number(), - parameters["blue"].number() - ); - ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms", - high.red, high.green, high.blue, length, interval); - led_strip_->Scroll(low, high, length, interval); - }); } diff --git a/main/boards/kevin-c3/led_strip_control.h b/main/boards/kevin-c3/led_strip_control.h index d8cf8324..0e99bb36 100644 --- a/main/boards/kevin-c3/led_strip_control.h +++ b/main/boards/kevin-c3/led_strip_control.h @@ -1,12 +1,9 @@ #ifndef LED_STRIP_CONTROL_H #define LED_STRIP_CONTROL_H -#include "iot/thing.h" #include "led/circular_strip.h" -using namespace iot; - -class LedStripControl : public Thing { +class LedStripControl { private: CircularStrip* led_strip_; int brightness_level_; // 亮度等级 (0-8) diff --git a/main/boards/lichuang-dev/lichuang_dev_board.cc b/main/boards/lichuang-dev/lichuang_dev_board.cc index 996449cd..658db9ee 100644 --- a/main/boards/lichuang-dev/lichuang_dev_board.cc +++ b/main/boards/lichuang-dev/lichuang_dev_board.cc @@ -116,6 +116,15 @@ private: } app.ToggleChatState(); }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif } void InitializeSt7789Display() { diff --git a/main/boards/minsi-k08-dual/config.h b/main/boards/minsi-k08-dual/config.h index 920243cd..0bdecb08 100644 --- a/main/boards/minsi-k08-dual/config.h +++ b/main/boards/minsi-k08-dual/config.h @@ -40,4 +40,8 @@ #define DISPLAY_OFFSET_Y 0 #define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +// A MCP Test: Control a lamp +#define LAMP_GPIO GPIO_NUM_18 + #endif // _BOARD_CONFIG_H_ diff --git a/main/boards/minsi-k08-dual/minsi_k08_dual.cc b/main/boards/minsi-k08-dual/minsi_k08_dual.cc index 3b3f7035..49da0ad3 100644 --- a/main/boards/minsi-k08-dual/minsi_k08_dual.cc +++ b/main/boards/minsi-k08-dual/minsi_k08_dual.cc @@ -7,6 +7,8 @@ #include "button.h" #include "config.h" #include "power_save_timer.h" +#include "mcp_server.h" +#include "lamp_controller.h" #include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" @@ -195,11 +197,15 @@ private: // 物联网初始化,添加对 AI 可见设备 void InitializeIot() { +#if CONFIG_IOT_PROTOCOL_XIAOZHI auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Screen")); thing_manager.AddThing(iot::CreateThing("Lamp")); thing_manager.AddThing(iot::CreateThing("Battery")); +#elif CONFIG_IOT_PROTOCOL_MCP + static LampController lamp(LAMP_GPIO); +#endif } diff --git a/main/boards/waveshare-s3-touch-amoled-1.75/board_control.cc b/main/boards/waveshare-s3-touch-amoled-1.75/board_control.cc deleted file mode 100644 index 96c11079..00000000 --- a/main/boards/waveshare-s3-touch-amoled-1.75/board_control.cc +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include -#include -#include - -#include "board.h" -#include "boards/common/wifi_board.h" -#include "boards/waveshare-s3-touch-amoled-1.75/config.h" -#include "iot/thing.h" - -#define TAG "BoardControl" - -namespace iot { - -class BoardControl : public Thing { -public: - BoardControl() : Thing("BoardControl", "当前 AI 机器人管理和控制") { - // 修改重新配网 - methods_.AddMethod("ResetWifiConfiguration", "重新配网", ParameterList(), - [this](const ParameterList& parameters) { - ESP_LOGI(TAG, "ResetWifiConfiguration"); - auto board = static_cast(&Board::GetInstance()); - if (board && board->GetBoardType() == "wifi") { - board->ResetWifiConfiguration(); - } - }); - } -}; - -} // namespace iot - -DECLARE_THING(BoardControl); diff --git a/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc b/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc index e602ea45..86b803fe 100644 --- a/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc +++ b/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc @@ -7,7 +7,7 @@ #include "application.h" #include "button.h" #include "led/single_led.h" -#include "iot/thing_manager.h" +#include "mcp_server.h" #include "config.h" #include "power_save_timer.h" #include "axp2101.h" @@ -221,7 +221,17 @@ private: if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); } - app.ToggleChatState(); }); + app.ToggleChatState(); + }); + +#if CONFIG_USE_DEVICE_AEC + boot_button_.OnDoubleClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateIdle) { + app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); + } + }); +#endif } void InitializeSH8601Display() { @@ -294,13 +304,16 @@ private: ESP_LOGI(TAG, "Touch panel initialized successfully"); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - thing_manager.AddThing(iot::CreateThing("BoardControl")); + // 初始化工具 + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "Reboot the device and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + ResetWifiConfiguration(); + return true; + }); } public: @@ -313,7 +326,7 @@ public: InitializeSH8601Display(); InitializeTouch(); InitializeButtons(); - InitializeIot(); + InitializeTools(); } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/boards/xmini-c3/xmini_c3_board.cc b/main/boards/xmini-c3/xmini_c3_board.cc index 9e765b57..2c3011a2 100644 --- a/main/boards/xmini-c3/xmini_c3_board.cc +++ b/main/boards/xmini-c3/xmini_c3_board.cc @@ -4,7 +4,7 @@ #include "application.h" #include "button.h" #include "led/single_led.h" -#include "iot/thing_manager.h" +#include "mcp_server.h" #include "settings.h" #include "config.h" #include "power_save_timer.h" @@ -142,14 +142,32 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { + void InitializeTools() { Settings settings("vendor"); press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0; - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("PressToTalk")); +#if CONFIG_IOT_PROTOCOL_XIAOZHI +#error "XiaoZhi 协议不支持" +#elif CONFIG_IOT_PROTOCOL_MCP + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.set_press_to_talk", + "Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n" + "The mode can be `press_to_talk` or `click_to_talk`.", + PropertyList({ + Property("mode", kPropertyTypeString) + }), + [this](const PropertyList& properties) -> ReturnValue { + auto mode = properties["mode"].value(); + if (mode == "press_to_talk") { + SetPressToTalkEnabled(true); + return true; + } else if (mode == "click_to_talk") { + SetPressToTalkEnabled(false); + return true; + } + throw std::runtime_error("Invalid mode: " + mode); + }); +#endif } public: @@ -161,7 +179,7 @@ public: InitializeSsd1306Display(); InitializeButtons(); InitializePowerSaveTimer(); - InitializeIot(); + InitializeTools(); } virtual Led* GetLed() override { @@ -194,30 +212,3 @@ public: }; DECLARE_BOARD(XminiC3Board); - - -namespace iot { - -class PressToTalk : public Thing { -public: - PressToTalk() : Thing("PressToTalk", "控制对话模式,一种是长按对话,一种是单击后连续对话。") { - // 定义设备的属性 - properties_.AddBooleanProperty("enabled", "true 表示长按说话模式,false 表示单击说话模式", []() -> bool { - auto board = static_cast(&Board::GetInstance()); - return board->IsPressToTalkEnabled(); - }); - - // 定义设备可以被远程执行的指令 - methods_.AddMethod("SetEnabled", "启用或禁用长按说话模式,调用前需要经过用户确认", ParameterList({ - Parameter("enabled", "true 表示长按说话模式,false 表示单击说话模式", kValueTypeBoolean, true) - }), [](const ParameterList& parameters) { - bool enabled = parameters["enabled"].boolean(); - auto board = static_cast(&Board::GetInstance()); - board->SetPressToTalkEnabled(enabled); - }); - } -}; - -} // namespace iot - -DECLARE_THING(PressToTalk); diff --git a/main/boards/zhengchen-1.54tft-wifi/temperature.cc b/main/boards/zhengchen-1.54tft-wifi/temperature.cc deleted file mode 100644 index e01e409e..00000000 --- a/main/boards/zhengchen-1.54tft-wifi/temperature.cc +++ /dev/null @@ -1,29 +0,0 @@ -#include "iot/thing.h" -#include "board.h" - -#include - -#define TAG "Temperature" - -namespace iot { - -class Temperature : public Thing { -private: - float esp32temp = 0.0f; -public: - Temperature() : Thing("Temperature", "芯片温度管理") { - // 定义设备的属性 - properties_.AddNumberProperty("temp", "当前芯片温度", [this]() -> float { - auto& board = Board::GetInstance(); - if (board.GetTemperature(esp32temp)) { - return esp32temp; - } - return 0; - }); - - } -}; - -} // namespace iot - -DECLARE_THING(Temperature); \ No newline at end of file diff --git a/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc b/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc index 94ba3a6b..edc8b501 100644 --- a/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc +++ b/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc @@ -180,10 +180,12 @@ private: } void InitializeIot() { +#if CONFIG_IOT_PROTOCOL_XIAOZHI auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Screen")); thing_manager.AddThing(iot::CreateThing("Battery")); +#endif } public: diff --git a/main/mcp_server.cc b/main/mcp_server.cc index 9d617815..42e31c8a 100644 --- a/main/mcp_server.cc +++ b/main/mcp_server.cc @@ -16,7 +16,6 @@ #define TAG "MCP" McpServer::McpServer() { - AddCommonTools(); } McpServer::~McpServer() { @@ -27,6 +26,10 @@ McpServer::~McpServer() { } void McpServer::AddCommonTools() { + // To speed up the response time, we add the common tools to the beginning of + // the tools list to utilize the prompt cache. + // Backup the original tools list and restore it after adding the common tools. + auto original_tools = std::move(tools_); auto& board = Board::GetInstance(); AddTool("self.get_device_status", @@ -96,9 +99,18 @@ void McpServer::AddCommonTools() { return camera->Explain(question); }); } + + // Restore the original tools list to the end of the tools list + tools_.insert(tools_.end(), original_tools.begin(), original_tools.end()); } void McpServer::AddTool(McpTool* tool) { + // Prevent adding duplicate tools + if (std::find_if(tools_.begin(), tools_.end(), [tool](const McpTool* t) { return t->name() == tool->name(); }) != tools_.end()) { + ESP_LOGW(TAG, "Tool %s already added", tool->name().c_str()); + return; + } + ESP_LOGI(TAG, "Add tool: %s", tool->name().c_str()); tools_.push_back(tool); } diff --git a/main/mcp_server.h b/main/mcp_server.h index f0c3144c..a5e3bae0 100644 --- a/main/mcp_server.h +++ b/main/mcp_server.h @@ -255,6 +255,7 @@ public: return instance; } + void AddCommonTools(); void AddTool(McpTool* tool); void AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function callback); void ParseMessage(const cJSON* json); @@ -264,7 +265,6 @@ private: McpServer(); ~McpServer(); - void AddCommonTools(); void ParseCapabilities(const cJSON* capabilities); void ReplyResult(int id, const std::string& result);