diff --git a/main/boards/esp-hi/README.md b/main/boards/esp-hi/README.md new file mode 100644 index 00000000..8921f1f7 --- /dev/null +++ b/main/boards/esp-hi/README.md @@ -0,0 +1,51 @@ +# ESP-Hi + +## 简介 + +
+ 立创开源平台 + | + Bilibili +
+ +ESP-Hi 是 ESP Friends 开源的一款基于 ESP32C3 的超**低成本** AI 对话机器人。ESP-Hi 集成了一个0.96寸的彩屏,用于显示表情,**机器狗已实现数十种动作**。通过对 ESP32-C3 外设的充分挖掘,仅需最少的板级硬件即可实现拾音和发声,同步优化了软件,降低内存与 Flash 占用,在资源受限的情况下同时实现了**唤醒词检测**与多种外设驱动。硬件详情等可查看[立创开源项目](https://oshwhub.com/esp-college/esp-hi)。 + +## WebUI + +ESP-Hi x 小智内置了一个控制身体运动的 WebUI,请将手机与 ESP-Hi 连接到同一个 Wi-Fi 下,手机访问 `http://esp-hi.local/` 以使用。 + +如需禁用,请取消 `ESP_HI_WEB_CONTROL_ENABLED`,即取消勾选 `Component config` → `Servo Dog Configuration` → `Web Control` → `Enable ESP-HI Web Control`。 + +## 配置、编译命令 + +由于 ESP-Hi 需要配置较多的 sdkconfig 选项,推荐使用编译脚本编译。 + +**编译** + +```bash +python ./scripts/release.py esp-hi +``` + +如需手动编译,请参考 `esp-hi/config.json` 修改 menuconfig 对应选项。 + +**烧录** + +```bash +idf.py flash +``` + + +> [!TIP] +> +> **舵机控制会占用 ESP-Hi 的 USB Type-C 接口**,导致无法连接电脑(无法烧录/查看运行日志)。如遇此情况,请按以下提示操作: +> +> **烧录** +> +> 1. 断开 ESP-Hi 的电源,只留头部,不要连接身体。 +> 2. 按住 ESP-Hi 的按钮并连接电脑。 +> +> 此时,ESP-Hi (ESP32C3) 应当处于烧录模式,可以使用电脑烧录程序。烧录完成后,可能需要重新插拔电源。 +> +> **查看 log** +> +> 请设置 `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y`,即 `Component config` → `ESP System Settings` → `Channel for console output` 选择 `USB Serial/JTAG Controller`。这同时会禁用舵机控制功能。 diff --git a/main/boards/esp-hi/config.json b/main/boards/esp-hi/config.json index f432c446..aaaf5527 100644 --- a/main/boards/esp-hi/config.json +++ b/main/boards/esp-hi/config.json @@ -31,7 +31,7 @@ "CONFIG_NEWLIB_NANO_FORMAT=y", "CONFIG_MMAP_FILE_NAME_LENGTH=25", "CONFIG_ESP_CONSOLE_NONE=y", - "CONFIG_IOT_PROTOCOL_XIAOZHI=y" + "CONFIG_IOT_PROTOCOL_MCP=y" ] } ] diff --git a/main/boards/esp-hi/dog_action extra.cc b/main/boards/esp-hi/dog_action extra.cc deleted file mode 100644 index 59b3ab84..00000000 --- a/main/boards/esp-hi/dog_action extra.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "iot/thing.h" -#include "board.h" -#include "audio_codec.h" - -#include -#include -#include "servo_dog_ctrl.h" - -#define TAG "Message" - -namespace iot { - -class DogAction_extra : public Thing { -private: - bool is_moving_ = false; - - void InitializePlayer() - { - ESP_LOGI(TAG, "Dog action initialized"); - } - -public: - DogAction_extra() : Thing("DogAction_extra", "机器人扩展动作控制") - { - InitializePlayer(); - - // 定义设备的属性 - properties_.AddBooleanProperty("is_moving", "机器人是否正在移动", [this]() -> bool { - return is_moving_; - }); - - // 定义设备可以被远程执行的指令 - methods_.AddMethod("retract_legs", "机器人收回腿部", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL); - }); - - methods_.AddMethod("stop", "立即停止机器人当前动作", ParameterList(), [this](const ParameterList & parameters) { - if (is_moving_) { - is_moving_ = false; - servo_dog_ctrl_send(DOG_STATE_IDLE, NULL); - } - }); - - methods_.AddMethod("shake_hand", "机器人做握手动作", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL); - }); - - methods_.AddMethod("shake_back_legs", "机器人伸懒腰", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL); - }); - - methods_.AddMethod("jump_forward", "机器人向前跳跃", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL); - }); - } -}; - -} // namespace iot - -DECLARE_THING(DogAction_extra); diff --git a/main/boards/esp-hi/dog_action_basic.cc b/main/boards/esp-hi/dog_action_basic.cc deleted file mode 100644 index 39765d9a..00000000 --- a/main/boards/esp-hi/dog_action_basic.cc +++ /dev/null @@ -1,75 +0,0 @@ -#include "iot/thing.h" -#include "board.h" -#include "audio_codec.h" - -#include -#include -#include "servo_dog_ctrl.h" - -#define TAG "Message" - -namespace iot { - -class DogAction_basic : public Thing { -private: - bool is_moving_ = false; - - void InitializePlayer() - { - ESP_LOGI(TAG, "Dog action initialized"); - } - -public: - DogAction_basic() : Thing("DogAction_basic", "机器人基础动作控制") - { - InitializePlayer(); - - // 定义设备的属性 - properties_.AddBooleanProperty("is_moving", "机器人是否正在移动", [this]() -> bool { - return is_moving_; - }); - - // 定义设备可以被远程执行的指令 - methods_.AddMethod("forward", "机器人向前移动", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL); - }); - - methods_.AddMethod("backward", "机器人向后移动", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL); - }); - - methods_.AddMethod("sway_back_forth", "机器人做前后摇摆动作", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL); - }); - - methods_.AddMethod("turn_left", "机器人向左转", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL); - }); - - methods_.AddMethod("turn_right", "机器人向右转", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL); - }); - - methods_.AddMethod("lay_down", "机器人趴下", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL); - }); - - methods_.AddMethod("sway", "机器人做左右摇摆动作", ParameterList(), [this](const ParameterList & parameters) { - is_moving_ = true; - dog_action_args_t args = { - .repeat_count = 4, - }; - servo_dog_ctrl_send(DOG_STATE_SWAY, &args); - }); - } -}; - -} // namespace iot - -DECLARE_THING(DogAction_basic); diff --git a/main/boards/esp-hi/dog_light.cc b/main/boards/esp-hi/dog_light.cc deleted file mode 100644 index 5d6d2fc5..00000000 --- a/main/boards/esp-hi/dog_light.cc +++ /dev/null @@ -1,113 +0,0 @@ -#include "iot/thing.h" -#include "board.h" -#include "audio_codec.h" - -#include -#include -#include "driver/rmt_tx.h" -#include "led_strip.h" - -#define TAG "Light" - -static led_strip_handle_t led_strip; - -static const led_strip_config_t bsp_strip_config = { - .strip_gpio_num = GPIO_NUM_8, - .max_leds = 4, - .led_model = LED_MODEL_WS2812, - .flags = { - .invert_out = false - } -}; - -static const led_strip_rmt_config_t bsp_rmt_config = { - .clk_src = RMT_CLK_SRC_DEFAULT, - .resolution_hz = 10 * 1000 * 1000, - .flags = { - .with_dma = false - } -}; - -esp_err_t bsp_led_init() -{ - ESP_LOGI(TAG, "BLINK_GPIO setting %d", bsp_strip_config.strip_gpio_num); - - ESP_ERROR_CHECK(led_strip_new_rmt_device(&bsp_strip_config, &bsp_rmt_config, &led_strip)); - led_strip_set_pixel(led_strip, 0, 0x00, 0x00, 0x00); - led_strip_set_pixel(led_strip, 1, 0x00, 0x00, 0x00); - led_strip_set_pixel(led_strip, 2, 0x00, 0x00, 0x00); - led_strip_set_pixel(led_strip, 3, 0x00, 0x00, 0x00); - led_strip_refresh(led_strip); - - return ESP_OK; -} - -esp_err_t bsp_led_rgb_set(uint8_t r, uint8_t g, uint8_t b) -{ - esp_err_t ret = ESP_OK; - - ret |= led_strip_set_pixel(led_strip, 0, r, g, b); - ret |= led_strip_set_pixel(led_strip, 1, r, g, b); - ret |= led_strip_set_pixel(led_strip, 2, r, g, b); - ret |= led_strip_set_pixel(led_strip, 3, r, g, b); - ret |= led_strip_refresh(led_strip); - return ret; -} - -namespace iot { -class DogLight : public Thing { -private: - bool power_ = false; - - void InitializeGpio() - { - bsp_led_init(); - bsp_led_rgb_set(0x00, 0x00, 0x00); - ESP_LOGI(TAG, "lamp InitializeGpio"); - } - -public: - DogLight() : Thing("DogLight", "机器人头灯"), power_(false) - { - InitializeGpio(); - - properties_.AddBooleanProperty("power", "灯是否打开", [this]() -> bool { - return power_; - }); - - methods_.AddMethod("TurnOn", "打开灯", ParameterList(), [this](const ParameterList & parameters) { - power_ = true; - bsp_led_rgb_set(0xFF, 0xFF, 0xFF); - ESP_LOGI(TAG, "lamp TurnOn"); - }); - - methods_.AddMethod("TurnOff", "关闭灯", ParameterList(), [this](const ParameterList & parameters) { - power_ = false; - bsp_led_rgb_set(0x00, 0x00, 0x00); - ESP_LOGI(TAG, "lamp TurnOff"); - }); - - methods_.AddMethod("SetRGB", "设置RGB颜色", - ParameterList({ - Parameter("r", "红色值(0-255)", kValueTypeNumber, true), - Parameter("g", "绿色值(0-255)", kValueTypeNumber, true), - Parameter("b", "蓝色值(0-255)", kValueTypeNumber, true) - }), [this](const ParameterList & parameters) { - int r = parameters["r"].number(); - int g = parameters["g"].number(); - int b = parameters["b"].number(); - - r = std::max(0, std::min(255, r)); - g = std::max(0, std::min(255, g)); - b = std::max(0, std::min(255, b)); - - power_ = true; - bsp_led_rgb_set(r, g, b); - ESP_LOGI(TAG, "lamp SetRGB: r=%d, g=%d, b=%d", r, g, b); - }); - } -}; - -} // namespace iot - -DECLARE_THING(DogLight); diff --git a/main/boards/esp-hi/esp_hi.cc b/main/boards/esp-hi/esp_hi.cc index c6bb75b8..9ca851da 100644 --- a/main/boards/esp-hi/esp_hi.cc +++ b/main/boards/esp-hi/esp_hi.cc @@ -3,7 +3,7 @@ #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" +#include "mcp_server.h" #include #include #include @@ -21,8 +21,14 @@ #include "anim_player.h" #include "emoji_display.h" #include "servo_dog_ctrl.h" +#include "led_strip.h" +#include "driver/rmt_tx.h" +#include "sdkconfig.h" + +#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED #include "esp_hi_web_control.h" +#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED #define TAG "ESP_HI" @@ -47,6 +53,23 @@ static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { {0x2C, NULL, 0, 0}, // Memory write }; +static const led_strip_config_t bsp_strip_config = { + .strip_gpio_num = GPIO_NUM_8, + .max_leds = 4, + .led_model = LED_MODEL_WS2812, + .flags = { + .invert_out = false + } +}; + +static const led_strip_rmt_config_t bsp_rmt_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10 * 1000 * 1000, + .flags = { + .with_dma = false + } +}; + class EspHi : public WifiBoard { private: Button boot_button_; @@ -54,7 +77,10 @@ private: Button move_wake_button_; anim::EmojiWidget* display_ = nullptr; bool web_server_initialized_ = false; + led_strip_handle_t led_strip_; + bool led_on_ = false; +#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { @@ -72,6 +98,7 @@ private: } } } +#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED void HandleMoveWakePressDown(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) { @@ -154,17 +181,39 @@ private: HandleMoveWakePressUp(current_time, last_trigger_time, gesture_state); }); } + + void InitializeLed() { + ESP_LOGI(TAG, "BLINK_GPIO setting %d", bsp_strip_config.strip_gpio_num); + + ESP_ERROR_CHECK(led_strip_new_rmt_device(&bsp_strip_config, &bsp_rmt_config, &led_strip_)); + led_strip_set_pixel(led_strip_, 0, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip_, 1, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip_, 2, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip_, 3, 0x00, 0x00, 0x00); + led_strip_refresh(led_strip_); + } + + esp_err_t SetLedColor(uint8_t r, uint8_t g, uint8_t b) { + esp_err_t ret = ESP_OK; + + ret |= led_strip_set_pixel(led_strip_, 0, r, g, b); + ret |= led_strip_set_pixel(led_strip_, 1, r, g, b); + ret |= led_strip_set_pixel(led_strip_, 2, r, g, b); + ret |= led_strip_set_pixel(led_strip_, 3, r, g, b); + ret |= led_strip_refresh(led_strip_); + return ret; + } void InitializeIot() { ESP_LOGI(TAG, "Initialize Iot"); - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("DogLight")); - thing_manager.AddThing(iot::CreateThing("DogAction_basic")); - thing_manager.AddThing(iot::CreateThing("DogAction_extra")); + InitializeLed(); + SetLedColor(0x00, 0x00, 0x00); +#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, &wifi_event_handler, this)); +#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED } void InitializeSpi() @@ -235,6 +284,107 @@ private: #endif } + void InitializeTools() + { + auto& mcp_server = McpServer::GetInstance(); + + // 基础动作控制 + mcp_server.AddTool("self.dog.forward", "机器人向前移动", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.backward", "机器人向后移动", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.sway_back_forth", "机器人做前后摇摆动作", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.turn_left", "机器人向左转", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.turn_right", "机器人向右转", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.lay_down", "机器人趴下", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.sway", "机器人做左右摇摆动作", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + dog_action_args_t args = { + .repeat_count = 4, + }; + servo_dog_ctrl_send(DOG_STATE_SWAY, &args); + return true; + }); + + // 扩展动作控制 + mcp_server.AddTool("self.dog.retract_legs", "机器人收回腿部", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.stop", "立即停止机器人当前动作", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_IDLE, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.shake_hand", "机器人做握手动作", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.shake_back_legs", "机器人伸懒腰", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL); + return true; + }); + + mcp_server.AddTool("self.dog.jump_forward", "机器人向前跳跃", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL); + return true; + }); + + // 灯光控制 + mcp_server.AddTool("self.light.get_power", "获取灯是否打开", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + return led_on_; + }); + + mcp_server.AddTool("self.light.turn_on", "打开灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SetLedColor(0xFF, 0xFF, 0xFF); + led_on_ = true; + return true; + }); + + mcp_server.AddTool("self.light.turn_off", "关闭灯", PropertyList(), [this](const PropertyList& properties) -> ReturnValue { + SetLedColor(0x00, 0x00, 0x00); + led_on_ = false; + return true; + }); + + mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({ + Property("r", kPropertyTypeInteger, 0, 255), + Property("g", kPropertyTypeInteger, 0, 255), + Property("b", kPropertyTypeInteger, 0, 255) + }), [this](const PropertyList& properties) -> ReturnValue { + int r = properties["r"].value(); + int g = properties["g"].value(); + int b = properties["b"].value(); + + led_on_ = true; + SetLedColor(r, g, b); + return true; + }); + } + public: EspHi() : boot_button_(BOOT_BUTTON_GPIO), audio_wake_button_(AUDIO_WAKE_BUTTON_GPIO), @@ -244,6 +394,7 @@ public: InitializeIot(); InitializeSpi(); InitializeLcdDisplay(); + InitializeTools(); } virtual AudioCodec* GetAudioCodec() override