diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 3ca15913..507addf1 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -61,6 +61,9 @@ list(APPEND SOURCES "boards/common/sy6970.cc" "boards/common/system_reset.cc" ) +if(CONFIG_XIAOZHI_USE_ETHERNET) + list(APPEND SOURCES "boards/common/ethernet_board.cc") +endif() list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/common) @@ -505,7 +508,7 @@ elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_32) set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) set(BUILTIN_ICON_FONT font_awesome_20_4) set(DEFAULT_EMOJI_COLLECTION twemoji_32) -elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO) +elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO OR CONFIG_ETH_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO) set(MANUFACTURER "waveshare") set(BOARD_TYPE "esp32-p4-nano") set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) @@ -1031,6 +1034,7 @@ idf_component_register(SRCS ${SOURCES} esp_pm esp_psram esp_netif + esp_eth esp_driver_gpio esp_driver_uart esp_driver_spi diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 1eafba8a..78ed5ab2 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -117,11 +117,39 @@ choice bool "Serbian" endchoice +choice XIAOZHI_NETWORK_TYPE + prompt "Network Type" + default XIAOZHI_NETWORK_WIFI + help + Select the network connection type before choosing the board type. + Board Type options are filtered according to this choice. + + config XIAOZHI_NETWORK_WIFI + bool "WiFi" + + config XIAOZHI_NETWORK_ETHERNET + bool "Ethernet" + depends on IDF_TARGET_ESP32P4 + select ETH_ENABLED + select ETH_USE_ESP32_EMAC + help + Use an onboard Ethernet port for network access. + Currently supported board: + - Waveshare ESP32-P4-NANO +endchoice + +config XIAOZHI_USE_ETHERNET + bool + default y if XIAOZHI_NETWORK_ETHERNET + default n + choice BOARD_TYPE prompt "Board Type" + depends on XIAOZHI_NETWORK_WIFI default BOARD_TYPE_BREAD_COMPACT_WIFI help Board type. 开发板类型 + config BOARD_TYPE_BREAD_COMPACT_WIFI bool "Bread Compact WiFi (面包板)" depends on IDF_TARGET_ESP32S3 @@ -577,6 +605,65 @@ choice BOARD_TYPE depends on IDF_TARGET_ESP32S3 endchoice +choice XIAOZHI_ETH_BOARD_TYPE + prompt "Board Type" + depends on XIAOZHI_NETWORK_ETHERNET + default ETH_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO + help + Board type with onboard Ethernet support. + + config ETH_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO + bool "Waveshare ESP32-P4-NANO" + depends on IDF_TARGET_ESP32P4 +endchoice + +menu "Ethernet Configuration" + depends on XIAOZHI_NETWORK_ETHERNET + + choice XIAOZHI_ETH_PHY_MODEL + prompt "Ethernet PHY Device" + default XIAOZHI_ETH_PHY_IP101 + + config XIAOZHI_ETH_PHY_IP101 + bool "IP101/IP101GR" + help + IP101/IP101GR 10/100M Ethernet PHY, used by Waveshare ESP32-P4-NANO. + endchoice + + config XIAOZHI_ETH_MDC_GPIO + int "Ethernet SMI MDC GPIO" + range 0 54 + default 31 if ETH_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO + default 31 + help + GPIO used by the Ethernet SMI MDC signal. + + config XIAOZHI_ETH_MDIO_GPIO + int "Ethernet SMI MDIO GPIO" + range 0 54 + default 52 if ETH_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO + default 52 + help + GPIO used by the Ethernet SMI MDIO signal. + + config XIAOZHI_ETH_PHY_ADDR + int "Ethernet PHY Address" + range -1 31 + default 1 + help + Set the PHY address according to the board schematic. + Use -1 to let the driver search for the PHY address. + + config XIAOZHI_ETH_PHY_RST_GPIO + int "Ethernet PHY Reset GPIO" + range -1 54 + default 51 if ETH_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO + default -1 + help + GPIO used to reset the Ethernet PHY. Set to -1 if not connected. + +endmenu + choice depends on BOARD_TYPE_LILYGO_T_DISPLAY_P4 prompt "Select the screen type" diff --git a/main/boards/common/ethernet_board.cc b/main/boards/common/ethernet_board.cc new file mode 100644 index 00000000..6427e196 --- /dev/null +++ b/main/boards/common/ethernet_board.cc @@ -0,0 +1,302 @@ +#include "ethernet_board.h" + +#include "audio_codec.h" +#include "display.h" +#include "sdkconfig.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static const char* TAG = "EthernetBoard"; + +static esp_eth_phy_t* CreatePhy(const eth_phy_config_t& phy_config) { +#if CONFIG_XIAOZHI_ETH_PHY_IP101 + return esp_eth_phy_new_ip101(&phy_config); +#else + return nullptr; +#endif +} + +static eth_esp32_emac_config_t CreateEmacConfig() { + eth_esp32_emac_config_t config = {}; + config.smi_gpio.mdc_num = CONFIG_XIAOZHI_ETH_MDC_GPIO; + config.smi_gpio.mdio_num = CONFIG_XIAOZHI_ETH_MDIO_GPIO; + config.interface = EMAC_DATA_INTERFACE_RMII; + config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN; + config.clock_config.rmii.clock_gpio = static_cast(50); + config.dma_burst_len = ETH_DMA_BURST_LEN_32; + config.intr_priority = 0; +#if SOC_EMAC_USE_MULTI_IO_MUX || SOC_EMAC_MII_USE_GPIO_MATRIX + config.emac_dataif_gpio.rmii.tx_en_num = 49; + config.emac_dataif_gpio.rmii.txd0_num = 34; + config.emac_dataif_gpio.rmii.txd1_num = 35; + config.emac_dataif_gpio.rmii.crs_dv_num = 28; + config.emac_dataif_gpio.rmii.rxd0_num = 29; + config.emac_dataif_gpio.rmii.rxd1_num = 30; +#endif +#if !SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK + config.clock_config_out_in.rmii.clock_mode = EMAC_CLK_EXT_IN; + config.clock_config_out_in.rmii.clock_gpio = static_cast(-1); +#endif + config.mdc_freq_hz = 0; + return config; +} + +std::string EthernetBoard::GetBoardType() { + return "ethernet"; +} + +void EthernetBoard::SetNetworkEventCallback(NetworkEventCallback callback) { + network_event_callback_ = std::move(callback); +} + +void EthernetBoard::StartNetwork() { + xTaskCreate(NetworkTaskEntry, "eth_net", 4096, this, 5, nullptr); +} + +void EthernetBoard::NetworkTaskEntry(void* arg) { + auto* board = static_cast(arg); + board->NetworkTask(); + vTaskDelete(nullptr); +} + +void EthernetBoard::NetworkTask() { + OnNetworkEvent(NetworkEvent::Connecting, "Ethernet"); + + esp_err_t ret = esp_netif_init(); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "Failed to initialize esp-netif: %s", esp_err_to_name(ret)); + OnNetworkEvent(NetworkEvent::Disconnected); + return; + } + + ret = esp_event_loop_create_default(); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "Failed to create default event loop: %s", esp_err_to_name(ret)); + OnNetworkEvent(NetworkEvent::Disconnected); + return; + } + + esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH(); + eth_netif_ = esp_netif_new(&netif_config); + if (eth_netif_ == nullptr) { + ESP_LOGE(TAG, "Failed to create Ethernet netif"); + OnNetworkEvent(NetworkEvent::Disconnected); + return; + } + + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.phy_addr = CONFIG_XIAOZHI_ETH_PHY_ADDR; + phy_config.reset_gpio_num = CONFIG_XIAOZHI_ETH_PHY_RST_GPIO; + + eth_esp32_emac_config_t emac_config = CreateEmacConfig(); + esp_eth_mac_t* mac = esp_eth_mac_new_esp32(&emac_config, &mac_config); + if (mac == nullptr) { + ESP_LOGE(TAG, "Failed to create Ethernet MAC"); + OnNetworkEvent(NetworkEvent::Disconnected); + return; + } + + esp_eth_phy_t* phy = CreatePhy(phy_config); + if (phy == nullptr) { + ESP_LOGE(TAG, "Failed to create Ethernet PHY"); + mac->del(mac); + OnNetworkEvent(NetworkEvent::Disconnected); + return; + } + + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); + ret = esp_eth_driver_install(ð_config, ð_handle_); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to install Ethernet driver: %s", esp_err_to_name(ret)); + mac->del(mac); + phy->del(phy); + OnNetworkEvent(NetworkEvent::Disconnected); + return; + } + + eth_glue_ = esp_eth_new_netif_glue(eth_handle_); + if (eth_glue_ == nullptr) { + ESP_LOGE(TAG, "Failed to create Ethernet netif glue"); + esp_eth_driver_uninstall(eth_handle_); + eth_handle_ = nullptr; + mac->del(mac); + phy->del(phy); + OnNetworkEvent(NetworkEvent::Disconnected); + return; + } + + ESP_ERROR_CHECK(esp_netif_attach(eth_netif_, eth_glue_)); + ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, EthEventHandler, this)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, GotIpEventHandler, this)); + + ret = esp_eth_start(eth_handle_); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to start Ethernet: %s", esp_err_to_name(ret)); + OnNetworkEvent(NetworkEvent::Disconnected); + } +} + +void EthernetBoard::EthEventHandler(void* arg, const char* event_base, int32_t event_id, void* event_data) { + auto* board = static_cast(arg); + uint8_t mac_addr[6] = {0}; + esp_eth_handle_t eth_handle = *(esp_eth_handle_t*)event_data; + + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: + esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); + ESP_LOGI(TAG, "Ethernet link up, MAC %02x:%02x:%02x:%02x:%02x:%02x", + mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); + break; + case ETHERNET_EVENT_DISCONNECTED: + ESP_LOGW(TAG, "Ethernet link down"); + board->connected_ = false; + board->ip_address_.clear(); + board->OnNetworkEvent(NetworkEvent::Disconnected); + break; + case ETHERNET_EVENT_START: + ESP_LOGI(TAG, "Ethernet started"); + break; + case ETHERNET_EVENT_STOP: + ESP_LOGI(TAG, "Ethernet stopped"); + board->connected_ = false; + board->ip_address_.clear(); + board->OnNetworkEvent(NetworkEvent::Disconnected); + break; + default: + break; + } +} + +void EthernetBoard::GotIpEventHandler(void* arg, const char* event_base, int32_t event_id, void* event_data) { + auto* board = static_cast(arg); + auto* event = static_cast(event_data); + const esp_netif_ip_info_t* ip_info = &event->ip_info; + + char ip[16] = {0}; + snprintf(ip, sizeof(ip), IPSTR, IP2STR(&ip_info->ip)); + board->connected_ = true; + board->ip_address_ = ip; + + ESP_LOGI(TAG, "Ethernet got IP: %s", ip); + board->OnNetworkEvent(NetworkEvent::Connected, "Ethernet"); +} + +void EthernetBoard::OnNetworkEvent(NetworkEvent event, const std::string& data) { + switch (event) { + case NetworkEvent::Connecting: + ESP_LOGI(TAG, "Ethernet connecting"); + break; + case NetworkEvent::Connected: + ESP_LOGI(TAG, "Ethernet connected"); + break; + case NetworkEvent::Disconnected: + ESP_LOGW(TAG, "Ethernet disconnected"); + break; + default: + break; + } + + if (network_event_callback_) { + network_event_callback_(event, data); + } +} + +NetworkInterface* EthernetBoard::GetNetwork() { + static EspNetwork network; + return &network; +} + +const char* EthernetBoard::GetNetworkStateIcon() { + return connected_ ? FONT_AWESOME_SIGNAL_STRONG : FONT_AWESOME_SIGNAL_OFF; +} + +std::string EthernetBoard::GetEthernetMacAddress() { + uint8_t mac[6] = {0}; + esp_read_mac(mac, ESP_MAC_ETH); + char mac_str[18]; + snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return std::string(mac_str); +} + +std::string EthernetBoard::GetBoardJson() { + std::string json = R"({"type":")" + std::string(BOARD_TYPE) + R"(",)"; + json += R"("name":")" + std::string(BOARD_NAME) + R"(",)"; + json += R"("network":"ethernet",)"; + if (!ip_address_.empty()) { + json += R"("ip":")" + ip_address_ + R"(",)"; + } + json += R"("mac":")" + GetEthernetMacAddress() + R"("})"; + return json; +} + +void EthernetBoard::SetPowerSaveLevel(PowerSaveLevel level) { + (void)level; +} + +std::string EthernetBoard::GetDeviceStatusJson() { + auto& board = Board::GetInstance(); + auto root = cJSON_CreateObject(); + + auto audio_speaker = cJSON_CreateObject(); + if (auto codec = board.GetAudioCodec()) { + cJSON_AddNumberToObject(audio_speaker, "volume", codec->output_volume()); + } + cJSON_AddItemToObject(root, "audio_speaker", audio_speaker); + + auto screen = cJSON_CreateObject(); + if (auto backlight = board.GetBacklight()) { + cJSON_AddNumberToObject(screen, "brightness", backlight->brightness()); + } + if (auto display = board.GetDisplay(); display && display->height() > 64) { + if (auto theme = display->GetTheme()) { + cJSON_AddStringToObject(screen, "theme", theme->name().c_str()); + } + } + cJSON_AddItemToObject(root, "screen", screen); + + int level = 0; + bool charging = false; + bool discharging = false; + if (board.GetBatteryLevel(level, charging, discharging)) { + auto battery = cJSON_CreateObject(); + cJSON_AddNumberToObject(battery, "level", level); + cJSON_AddBoolToObject(battery, "charging", charging); + cJSON_AddItemToObject(root, "battery", battery); + } + + auto network = cJSON_CreateObject(); + cJSON_AddStringToObject(network, "type", "ethernet"); + cJSON_AddStringToObject(network, "state", connected_ ? "connected" : "disconnected"); + if (!ip_address_.empty()) { + cJSON_AddStringToObject(network, "ip", ip_address_.c_str()); + } + cJSON_AddItemToObject(root, "network", network); + + float temp = 0.0f; + if (board.GetTemperature(temp)) { + auto chip = cJSON_CreateObject(); + cJSON_AddNumberToObject(chip, "temperature", temp); + cJSON_AddItemToObject(root, "chip", chip); + } + + auto str = cJSON_PrintUnformatted(root); + std::string result(str); + cJSON_free(str); + cJSON_Delete(root); + return result; +} diff --git a/main/boards/common/ethernet_board.h b/main/boards/common/ethernet_board.h new file mode 100644 index 00000000..d2e480cf --- /dev/null +++ b/main/boards/common/ethernet_board.h @@ -0,0 +1,41 @@ +#ifndef ETHERNET_BOARD_H +#define ETHERNET_BOARD_H + +#include "board.h" + +#include + +class EthernetBoard : public Board { +protected: + void* eth_handle_ = nullptr; + void* eth_glue_ = nullptr; + struct esp_netif_obj* eth_netif_ = nullptr; + NetworkEventCallback network_event_callback_ = nullptr; + bool connected_ = false; + std::string ip_address_; + + virtual void OnNetworkEvent(NetworkEvent event, const std::string& data = ""); + virtual std::string GetEthernetMacAddress(); + virtual std::string GetBoardJson() override; + +private: + static void NetworkTaskEntry(void* arg); + static void EthEventHandler(void* arg, const char* event_base, int32_t event_id, void* event_data); + static void GotIpEventHandler(void* arg, const char* event_base, int32_t event_id, void* event_data); + void NetworkTask(); + +public: + EthernetBoard() = default; + virtual ~EthernetBoard() = default; + + virtual std::string GetBoardType() override; + virtual void StartNetwork() override; + virtual NetworkInterface* GetNetwork() override; + virtual void SetNetworkEventCallback(NetworkEventCallback callback) override; + virtual const char* GetNetworkStateIcon() override; + virtual void SetPowerSaveLevel(PowerSaveLevel level) override; + virtual AudioCodec* GetAudioCodec() override { return nullptr; } + virtual std::string GetDeviceStatusJson() override; +}; + +#endif // ETHERNET_BOARD_H diff --git a/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc b/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc index 7fe1f0dc..58d46335 100644 --- a/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc +++ b/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc @@ -1,4 +1,10 @@ +#include "sdkconfig.h" + +#if CONFIG_XIAOZHI_USE_ETHERNET +#include "ethernet_board.h" +#else #include "wifi_board.h" +#endif #include "codecs/es8311_audio_codec.h" #include "application.h" #include "display/lcd_display.h" @@ -24,6 +30,12 @@ #include "esp_lcd_touch_gt911.h" #define TAG "WaveshareEsp32p4nano" +#if CONFIG_XIAOZHI_USE_ETHERNET +using WaveshareEsp32p4nanoBase = EthernetBoard; +#else +using WaveshareEsp32p4nanoBase = WifiBoard; +#endif + class CustomBacklight : public Backlight { public: CustomBacklight(i2c_master_bus_handle_t i2c_handle) @@ -61,7 +73,7 @@ protected: } }; -class WaveshareEsp32p4nano : public WifiBoard { +class WaveshareEsp32p4nano : public WaveshareEsp32p4nanoBase { private: i2c_master_bus_handle_t codec_i2c_bus_; Button boot_button_; @@ -241,12 +253,18 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); +#if CONFIG_XIAOZHI_USE_ETHERNET + if (app.GetDeviceState() != kDeviceStateStarting) { + app.ToggleChatState(); + } +#else // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot if (app.GetDeviceState() == kDeviceStateStarting) { EnterWifiConfigMode(); return; } app.ToggleChatState(); +#endif }); } diff --git a/main/display/lvgl_display/jpg/image_to_jpeg.cpp b/main/display/lvgl_display/jpg/image_to_jpeg.cpp index 485ca8ba..5df12a2b 100644 --- a/main/display/lvgl_display/jpg/image_to_jpeg.cpp +++ b/main/display/lvgl_display/jpg/image_to_jpeg.cpp @@ -144,7 +144,7 @@ static uint8_t* convert_input_to_encoder_buf(const uint8_t* src, uint16_t width, break; [[unlikely]] default: ESP_LOGE(TAG, "[Unreachable Case] unsupported format: 0x%08lx", format); - std::unreachable(); + return nullptr; } int sz = (int)width * (int)height * 2; uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16); diff --git a/main/system_info.cc b/main/system_info.cc index 3c011d10..cc589a9c 100644 --- a/main/system_info.cc +++ b/main/system_info.cc @@ -9,7 +9,7 @@ #include #include #include -#if CONFIG_IDF_TARGET_ESP32P4 +#if CONFIG_IDF_TARGET_ESP32P4 && !CONFIG_XIAOZHI_USE_ETHERNET #include "esp_wifi_remote.h" #endif @@ -34,7 +34,9 @@ size_t SystemInfo::GetFreeHeapSize() { std::string SystemInfo::GetMacAddress() { uint8_t mac[6]; -#if CONFIG_IDF_TARGET_ESP32P4 +#if CONFIG_XIAOZHI_USE_ETHERNET + esp_read_mac(mac, ESP_MAC_ETH); +#elif CONFIG_IDF_TARGET_ESP32P4 esp_wifi_get_mac(WIFI_IF_STA, mac); #else esp_read_mac(mac, ESP_MAC_WIFI_STA);