#include "wifi_board.h" #include "adc_pdm_audio_codec.h" #include "application.h" #include "button.h" #include "config.h" #include "mcp_server.h" #include #include #include #include #include #include #include "display/lcd_display.h" #include #include #include #include "esp_lcd_ili9341.h" #include "assets/lang_config.h" #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" static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { {0x11, NULL, 0, 120}, // Sleep out, Delay 120ms {0xB1, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0}, {0xB2, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0}, {0xB3, (uint8_t []){0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A}, 6, 0}, {0xB4, (uint8_t []){0x03}, 1, 0}, // Dot inversion {0xC0, (uint8_t []){0x44, 0x04, 0x04}, 3, 0}, {0xC1, (uint8_t []){0xC0}, 1, 0}, {0xC2, (uint8_t []){0x0D, 0x00}, 2, 0}, {0xC3, (uint8_t []){0x8D, 0x6A}, 2, 0}, {0xC4, (uint8_t []){0x8D, 0xEE}, 2, 0}, {0xC5, (uint8_t []){0x08}, 1, 0}, {0xE0, (uint8_t []){0x0F, 0x10, 0x03, 0x03, 0x07, 0x02, 0x00, 0x02, 0x07, 0x0C, 0x13, 0x38, 0x0A, 0x0E, 0x03, 0x10}, 16, 0}, {0xE1, (uint8_t []){0x10, 0x0B, 0x04, 0x04, 0x10, 0x03, 0x00, 0x03, 0x03, 0x09, 0x17, 0x33, 0x0B, 0x0C, 0x06, 0x10}, 16, 0}, {0x35, (uint8_t []){0x00}, 1, 0}, {0x3A, (uint8_t []){0x05}, 1, 0}, {0x36, (uint8_t []){0xC8}, 1, 0}, {0x29, NULL, 0, 0}, // Display on {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_; Button audio_wake_button_; 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) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { xTaskCreate( [](void* arg) { EspHi* instance = static_cast(arg); vTaskDelay(5000 / portTICK_PERIOD_MS); if (!instance->web_server_initialized_) { ESP_LOGI(TAG, "WiFi connected, init web control server"); esp_err_t err = esp_hi_web_control_server_init(); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize web control server: %d", err); } else { ESP_LOGI(TAG, "Web control server initialized"); instance->web_server_initialized_ = true; } } vTaskDelete(NULL); }, "web_server_init", 1024 * 10, arg, 5, nullptr); } } #endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED void HandleMoveWakePressDown(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) { int64_t interval = last_trigger_time == 0 ? 0 : current_time - last_trigger_time; last_trigger_time = current_time; if (interval > 1000) { gesture_state = 0; } else { switch (gesture_state) { case 0: break; case 1: if (interval > 300) { gesture_state = 2; } break; case 2: if (interval > 100) { gesture_state = 0; } break; } } } void HandleMoveWakePressUp(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) { int64_t interval = current_time - last_trigger_time; if (interval > 1000) { gesture_state = 0; } else { switch (gesture_state) { case 0: if (interval > 300) { gesture_state = 1; } break; case 1: break; case 2: if (interval < 100) { ESP_LOGI(TAG, "gesture detected"); gesture_state = 0; auto &app = Application::GetInstance(); app.ToggleChatState(); } break; } } } void InitializeButtons() { static int64_t last_trigger_time = 0; static int gesture_state = 0; // 0: init, 1: wait second long interval, 2: wait oscillation boot_button_.OnClick([this]() { auto &app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); } app.ToggleChatState(); }); audio_wake_button_.OnPressDown([this]() { }); audio_wake_button_.OnPressUp([this]() { }); move_wake_button_.OnPressDown([this]() { int64_t current_time = esp_timer_get_time() / 1000; HandleMoveWakePressDown(current_time, last_trigger_time, gesture_state); }); move_wake_button_.OnPressUp([this]() { int64_t current_time = esp_timer_get_time() / 1000; 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"); 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() { spi_bus_config_t buscfg = {}; buscfg.mosi_io_num = DISPLAY_MOSI_PIN; buscfg.miso_io_num = GPIO_NUM_NC; buscfg.sclk_io_num = DISPLAY_CLK_PIN; buscfg.quadwp_io_num = GPIO_NUM_NC; buscfg.quadhd_io_num = GPIO_NUM_NC; buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t); ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); } void InitializeLcdDisplay() { esp_lcd_panel_io_handle_t panel_io = nullptr; esp_lcd_panel_handle_t panel = nullptr; // 液晶屏控制IO初始化 ESP_LOGD(TAG, "Install panel IO"); esp_lcd_panel_io_spi_config_t io_config = {}; io_config.cs_gpio_num = DISPLAY_CS_PIN; io_config.dc_gpio_num = DISPLAY_DC_PIN; io_config.spi_mode = DISPLAY_SPI_MODE; io_config.pclk_hz = 40 * 1000 * 1000; io_config.trans_queue_depth = 10; io_config.lcd_cmd_bits = 8; io_config.lcd_param_bits = 8; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); // 初始化液晶屏驱动芯片 ESP_LOGD(TAG, "Install LCD driver"); const ili9341_vendor_config_t vendor_config = { .init_cmds = &vendor_specific_init[0], .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), }; esp_lcd_panel_dev_config_t panel_config = {}; panel_config.reset_gpio_num = DISPLAY_RST_PIN; panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; panel_config.bits_per_pixel = 16; panel_config.vendor_config = (void *) &vendor_config; ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); esp_lcd_panel_reset(panel); esp_lcd_panel_init(panel); esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); esp_lcd_panel_invert_color(panel, false); esp_lcd_panel_set_gap(panel, 0, 24); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); ESP_LOGI(TAG, "LCD panel create success, %p", panel); esp_lcd_panel_disp_on_off(panel, true); ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io); display_ = new anim::EmojiWidget(panel, panel_io); servo_dog_ctrl_config_t config = { .fl_gpio_num = FL_GPIO_NUM, .fr_gpio_num = FR_GPIO_NUM, .bl_gpio_num = BL_GPIO_NUM, .br_gpio_num = BR_GPIO_NUM, }; #if CONFIG_ESP_CONSOLE_NONE servo_dog_ctrl_init(&config); #endif } void InitializeTools() { auto& mcp_server = McpServer::GetInstance(); // 基础动作控制 mcp_server.AddTool("self.dog.basic_control", "机器人的基础动作。机器人可以做以下基础动作:\n" "forward: 向前移动\nbackward: 向后移动\nturn_left: 向左转\nturn_right: 向右转\nstop: 立即停止当前动作", PropertyList({ Property("action", kPropertyTypeString), }), [this](const PropertyList& properties) -> ReturnValue { const std::string& action = properties["action"].value(); if (action == "forward") { servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL); } else if (action == "backward") { servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL); } else if (action == "turn_left") { servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL); } else if (action == "turn_right") { servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL); } else if (action == "stop") { servo_dog_ctrl_send(DOG_STATE_IDLE, NULL); } else { return false; } return true; }); // 扩展动作控制 mcp_server.AddTool("self.dog.advanced_control", "机器人的扩展动作。机器人可以做以下扩展动作:\n" "sway_back_forth: 前后摇摆\nlay_down: 趴下\nsway: 左右摇摆\nretract_legs: 收回腿部\n" "shake_hand: 握手\nshake_back_legs: 伸懒腰\njump_forward: 向前跳跃", PropertyList({ Property("action", kPropertyTypeString), }), [this](const PropertyList& properties) -> ReturnValue { const std::string& action = properties["action"].value(); if (action == "sway_back_forth") { servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL); } else if (action == "lay_down") { servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL); } else if (action == "sway") { dog_action_args_t args = { .repeat_count = 4, }; servo_dog_ctrl_send(DOG_STATE_SWAY, &args); } else if (action == "retract_legs") { servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL); } else if (action == "shake_hand") { servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL); } else if (action == "shake_back_legs") { servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL); } else if (action == "jump_forward") { servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL); } else { return false; } 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), move_wake_button_(MOVE_WAKE_BUTTON_GPIO) { InitializeButtons(); InitializeIot(); InitializeSpi(); InitializeLcdDisplay(); InitializeTools(); } virtual AudioCodec* GetAudioCodec() override { static AdcPdmAudioCodec audio_codec( AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, AUDIO_ADC_MIC_CHANNEL, AUDIO_PDM_SPEAK_P_GPIO, AUDIO_PDM_SPEAK_N_GPIO, AUDIO_PA_CTL_GPIO); return &audio_codec; } virtual Display* GetDisplay() override { return display_; } }; DECLARE_BOARD(EspHi);