feat: add support for W5500 SPI Ethernet in gateway

- Introduced configuration options for wired Ethernet support in Kconfig and sdkconfig.
- Implemented Ethernet initialization and event handling in GatewayNetworkService.
- Enhanced app_main to manage Ethernet alongside Wi-Fi.
- Updated GatewayRuntime to store Ethernet information.
- Modified CMakeLists and include files to accommodate new Ethernet dependencies.
- Ensured backward compatibility by allowing Ethernet initialization failures to be ignored.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-12 08:42:10 +08:00
parent 36d10702da
commit 626f86ec4e
10 changed files with 600 additions and 17 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
idf_component_register(
SRCS "src/gateway_network.cpp"
INCLUDE_DIRS "include"
REQUIRES dali_domain esp_event esp_http_server esp_netif esp_wifi freertos gateway_bridge gateway_controller gateway_runtime log lwip espressif__cjson
REQUIRES dali_domain esp_driver_gpio esp_driver_spi esp_eth esp_event esp_http_server esp_hw_support esp_netif esp_wifi freertos gateway_bridge gateway_controller gateway_runtime log lwip espressif__cjson
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -8,6 +8,10 @@
#include "esp_err.h"
#include "esp_event.h"
#include "esp_eth.h"
#include "esp_eth_mac.h"
#include "esp_eth_netif_glue.h"
#include "esp_eth_phy.h"
#include "esp_http_server.h"
#include "esp_netif.h"
#include "esp_now.h"
@@ -26,6 +30,8 @@ struct DaliRawFrame;
struct GatewayNetworkServiceConfig {
bool wifi_enabled{true};
bool ethernet_enabled{false};
bool ethernet_ignore_init_failure{false};
bool espnow_setup_enabled{true};
bool espnow_setup_startup_enabled{false};
bool smartconfig_enabled{true};
@@ -35,6 +41,17 @@ struct GatewayNetworkServiceConfig {
bool udp_enabled{true};
uint16_t http_port{80};
uint16_t udp_port{2020};
int ethernet_spi_host{1};
int ethernet_spi_sclk_gpio{14};
int ethernet_spi_mosi_gpio{13};
int ethernet_spi_miso_gpio{12};
int ethernet_spi_cs_gpio{15};
int ethernet_spi_int_gpio{4};
uint32_t ethernet_poll_period_ms{0};
uint8_t ethernet_spi_clock_mhz{36};
int ethernet_phy_reset_gpio{5};
int ethernet_phy_addr{1};
uint32_t ethernet_rx_task_stack_size{3072};
int status_led_gpio{-1};
bool status_led_active_high{true};
int boot_button_gpio{-1};
@@ -66,12 +83,17 @@ class GatewayNetworkService {
static esp_err_t HandleLedOnGet(httpd_req_t* req);
static esp_err_t HandleLedOffGet(httpd_req_t* req);
static esp_err_t HandleJqJsGet(httpd_req_t* req);
static void HandleEthernetEvent(void* arg, esp_event_base_t event_base, int32_t event_id,
void* event_data);
static void HandleWifiEvent(void* arg, esp_event_base_t event_base, int32_t event_id,
void* event_data);
static void HandleEspNowReceive(const esp_now_recv_info_t* info, const uint8_t* data,
int data_len);
esp_err_t ensureNetworkStack();
esp_err_t startEthernet();
esp_err_t probeEthernetStartup();
void stopEthernet();
esp_err_t startWifi();
esp_err_t startSetupAp();
esp_err_t startSmartconfig();
@@ -89,6 +111,7 @@ class GatewayNetworkService {
void bootButtonTaskLoop();
void handleGatewayNotification(const std::vector<uint8_t>& frame);
void handleWifiControl(uint8_t mode);
void handleEthernetEvent(esp_event_base_t event_base, int32_t event_id, void* event_data);
void handleWifiEvent(esp_event_base_t event_base, int32_t event_id, void* event_data);
void handleEspNowReceive(const esp_now_recv_info_t* info, const uint8_t* data, int data_len);
void handleSetupUartFrame(int setup_id, const std::vector<uint8_t>& frame);
@@ -106,8 +129,15 @@ class GatewayNetworkService {
GatewayBridgeService* bridge_service_{nullptr};
bool started_{false};
httpd_handle_t http_server_{nullptr};
esp_netif_t* eth_netif_{nullptr};
esp_netif_t* wifi_sta_netif_{nullptr};
esp_netif_t* wifi_ap_netif_{nullptr};
esp_eth_handle_t eth_handle_{nullptr};
esp_eth_mac_t* eth_mac_{nullptr};
esp_eth_phy_t* eth_phy_{nullptr};
esp_eth_netif_glue_handle_t eth_glue_{nullptr};
bool ethernet_started_{false};
bool ethernet_event_handlers_registered_{false};
bool wifi_started_{false};
bool wifi_event_handlers_registered_{false};
bool setup_ap_started_{false};
@@ -7,8 +7,12 @@
#include "cJSON.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_event.h"
#include "esp_eth_driver.h"
#include "esp_eth_mac_spi.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include "esp_netif_ip_addr.h"
#include "esp_smartconfig.h"
@@ -268,6 +272,19 @@ esp_err_t GatewayNetworkService::start() {
return err;
}
if (config_.ethernet_enabled) {
err = startEthernet();
if (err != ESP_OK) {
if (config_.ethernet_ignore_init_failure) {
ESP_LOGW(kTag, "Ethernet init failed; Ethernet is disabled for this boot: %s",
esp_err_to_name(err));
config_.ethernet_enabled = false;
} else {
return err;
}
}
}
if (config_.espnow_setup_startup_enabled) {
err = startSetupAp();
if (err != ESP_OK) {
@@ -320,7 +337,8 @@ esp_err_t GatewayNetworkService::start() {
}
started_ = true;
ESP_LOGI(kTag, "network service started http=%d udp=%d", config_.http_enabled,
ESP_LOGI(kTag, "network service started eth=%d wifi=%d http=%d udp=%d",
config_.ethernet_enabled, config_.wifi_enabled, config_.http_enabled,
config_.udp_enabled);
return ESP_OK;
}
@@ -341,6 +359,234 @@ esp_err_t GatewayNetworkService::ensureNetworkStack() {
return ESP_OK;
}
esp_err_t GatewayNetworkService::startEthernet() {
if (ethernet_started_) {
return ESP_OK;
}
#if CONFIG_ETH_SPI_ETHERNET_W5500
if (eth_netif_ == nullptr) {
esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH();
eth_netif_ = esp_netif_new(&netif_config);
if (eth_netif_ == nullptr) {
ESP_LOGE(kTag, "failed to create Ethernet netif");
return ESP_ERR_NO_MEM;
}
}
if (!ethernet_event_handlers_registered_) {
esp_err_t err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID,
&GatewayNetworkService::HandleEthernetEvent, this);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to register Ethernet event handler: %s", esp_err_to_name(err));
stopEthernet();
return err;
}
err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP,
&GatewayNetworkService::HandleEthernetEvent, this);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to register Ethernet IP event handler: %s", esp_err_to_name(err));
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
ETH_EVENT, ESP_EVENT_ANY_ID, &GatewayNetworkService::HandleEthernetEvent));
stopEthernet();
return err;
}
ethernet_event_handlers_registered_ = true;
}
if (config_.ethernet_spi_int_gpio >= 0) {
esp_err_t err = gpio_install_isr_service(0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to install GPIO ISR service for Ethernet: %s", esp_err_to_name(err));
stopEthernet();
return err;
}
}
const auto spi_host = static_cast<spi_host_device_t>(config_.ethernet_spi_host);
spi_bus_config_t bus_config = {};
bus_config.miso_io_num = config_.ethernet_spi_miso_gpio;
bus_config.mosi_io_num = config_.ethernet_spi_mosi_gpio;
bus_config.sclk_io_num = config_.ethernet_spi_sclk_gpio;
bus_config.quadwp_io_num = -1;
bus_config.quadhd_io_num = -1;
esp_err_t err = spi_bus_initialize(spi_host, &bus_config, SPI_DMA_CH_AUTO);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to initialize Ethernet SPI host %d: %s", config_.ethernet_spi_host,
esp_err_to_name(err));
stopEthernet();
return err;
}
spi_device_interface_config_t spi_device_config = {};
spi_device_config.mode = 0;
spi_device_config.clock_speed_hz = static_cast<int>(config_.ethernet_spi_clock_mhz) * 1000 * 1000;
spi_device_config.spics_io_num = config_.ethernet_spi_cs_gpio;
spi_device_config.queue_size = 20;
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
mac_config.rx_task_stack_size = config_.ethernet_rx_task_stack_size;
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
phy_config.phy_addr = config_.ethernet_phy_addr;
phy_config.reset_gpio_num = config_.ethernet_phy_reset_gpio;
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_host, &spi_device_config);
w5500_config.int_gpio_num = config_.ethernet_spi_int_gpio;
w5500_config.poll_period_ms = config_.ethernet_poll_period_ms;
if (w5500_config.int_gpio_num < 0 && w5500_config.poll_period_ms == 0) {
w5500_config.poll_period_ms = 100;
}
eth_mac_ = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
if (eth_mac_ == nullptr) {
ESP_LOGE(kTag, "failed to create W5500 Ethernet MAC");
stopEthernet();
return ESP_ERR_NO_MEM;
}
eth_phy_ = esp_eth_phy_new_w5500(&phy_config);
if (eth_phy_ == nullptr) {
ESP_LOGE(kTag, "failed to create W5500 Ethernet PHY");
stopEthernet();
return ESP_ERR_NO_MEM;
}
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(eth_mac_, eth_phy_);
err = esp_eth_driver_install(&eth_config, &eth_handle_);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to install Ethernet driver: %s", esp_err_to_name(err));
stopEthernet();
return err;
}
uint8_t eth_mac[6] = {};
err = esp_read_mac(eth_mac, ESP_MAC_ETH);
if (err == ESP_OK) {
err = esp_eth_ioctl(eth_handle_, ETH_CMD_S_MAC_ADDR, eth_mac);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to set Ethernet MAC address: %s", esp_err_to_name(err));
stopEthernet();
return err;
}
EthernetInfo info;
info.mac = MacToHex(eth_mac);
runtime_.setEthernetInfo(std::move(info));
} else {
ESP_LOGW(kTag, "failed to read Ethernet MAC address: %s", esp_err_to_name(err));
}
eth_glue_ = esp_eth_new_netif_glue(eth_handle_);
if (eth_glue_ == nullptr) {
ESP_LOGE(kTag, "failed to create Ethernet netif glue");
stopEthernet();
return ESP_ERR_NO_MEM;
}
err = esp_netif_attach(eth_netif_, eth_glue_);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to attach Ethernet netif: %s", esp_err_to_name(err));
stopEthernet();
return err;
}
err = esp_eth_start(eth_handle_);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to start Ethernet: %s", esp_err_to_name(err));
stopEthernet();
return err;
}
ethernet_started_ = true;
err = probeEthernetStartup();
if (err != ESP_OK) {
ESP_LOGE(kTag, "Ethernet startup probe failed: %s", esp_err_to_name(err));
stopEthernet();
return err;
}
ESP_LOGI(kTag,
"Ethernet W5500 started spi_host=%d sclk=%d mosi=%d miso=%d cs=%d int=%d reset=%d",
config_.ethernet_spi_host, config_.ethernet_spi_sclk_gpio,
config_.ethernet_spi_mosi_gpio, config_.ethernet_spi_miso_gpio,
config_.ethernet_spi_cs_gpio, config_.ethernet_spi_int_gpio,
config_.ethernet_phy_reset_gpio);
return ESP_OK;
#else
ESP_LOGW(kTag, "Ethernet requested but W5500 support is not enabled in esp-eth");
return ESP_ERR_NOT_SUPPORTED;
#endif
}
esp_err_t GatewayNetworkService::probeEthernetStartup() {
if (eth_handle_ == nullptr || !ethernet_started_) {
return ESP_ERR_INVALID_STATE;
}
esp_err_t err = esp_eth_stop(eth_handle_);
ethernet_started_ = false;
if (err != ESP_OK) {
return err;
}
err = esp_eth_start(eth_handle_);
if (err == ESP_OK) {
ethernet_started_ = true;
}
return err;
}
void GatewayNetworkService::stopEthernet() {
if (ethernet_event_handlers_registered_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
IP_EVENT, IP_EVENT_ETH_GOT_IP, &GatewayNetworkService::HandleEthernetEvent));
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
ETH_EVENT, ESP_EVENT_ANY_ID, &GatewayNetworkService::HandleEthernetEvent));
ethernet_event_handlers_registered_ = false;
}
if (eth_handle_ != nullptr) {
const esp_err_t stop_err = esp_eth_stop(eth_handle_);
if (stop_err != ESP_OK && stop_err != ESP_ERR_INVALID_STATE) {
ESP_LOGW(kTag, "failed to stop Ethernet during disable: %s", esp_err_to_name(stop_err));
}
ethernet_started_ = false;
}
if (eth_glue_ != nullptr) {
const esp_err_t glue_err = esp_eth_del_netif_glue(eth_glue_);
if (glue_err != ESP_OK) {
ESP_LOGW(kTag, "failed to delete Ethernet netif glue: %s", esp_err_to_name(glue_err));
} else {
eth_glue_ = nullptr;
}
}
if (eth_handle_ != nullptr) {
const esp_err_t uninstall_err = esp_eth_driver_uninstall(eth_handle_);
if (uninstall_err != ESP_OK) {
ESP_LOGW(kTag, "failed to uninstall Ethernet driver: %s", esp_err_to_name(uninstall_err));
} else {
eth_handle_ = nullptr;
}
}
if (eth_phy_ != nullptr && eth_handle_ == nullptr) {
ESP_ERROR_CHECK_WITHOUT_ABORT(eth_phy_->del(eth_phy_));
eth_phy_ = nullptr;
}
if (eth_mac_ != nullptr && eth_handle_ == nullptr) {
ESP_ERROR_CHECK_WITHOUT_ABORT(eth_mac_->del(eth_mac_));
eth_mac_ = nullptr;
}
if (eth_netif_ != nullptr && eth_glue_ == nullptr) {
esp_netif_destroy(eth_netif_);
eth_netif_ = nullptr;
}
runtime_.clearEthernetInfo();
}
esp_err_t GatewayNetworkService::startWifi() {
if (wifi_started_) {
return ESP_OK;
@@ -778,6 +1024,69 @@ void GatewayNetworkService::HandleEspNowReceive(const esp_now_recv_info_t* info,
}
}
void GatewayNetworkService::HandleEthernetEvent(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
auto* service = static_cast<GatewayNetworkService*>(arg);
if (service != nullptr) {
service->handleEthernetEvent(event_base, event_id, event_data);
}
}
void GatewayNetworkService::handleEthernetEvent(esp_event_base_t event_base, int32_t event_id,
void* event_data) {
if (event_base == ETH_EVENT) {
esp_eth_handle_t handle = eth_handle_;
if (event_data != nullptr) {
handle = *static_cast<esp_eth_handle_t*>(event_data);
}
if (event_id == ETHERNET_EVENT_CONNECTED) {
uint8_t mac[6] = {};
if (handle != nullptr && esp_eth_ioctl(handle, ETH_CMD_G_MAC_ADDR, mac) == ESP_OK) {
const std::string mac_hex = MacToHex(mac);
EthernetInfo info = runtime_.deviceInfo().eth.value_or(EthernetInfo{});
info.mac = mac_hex;
runtime_.setEthernetInfo(std::move(info));
ESP_LOGI(kTag, "Ethernet link up mac=%s", mac_hex.c_str());
} else {
ESP_LOGI(kTag, "Ethernet link up");
}
return;
}
if (event_id == ETHERNET_EVENT_DISCONNECTED) {
runtime_.clearEthernetIp();
ESP_LOGI(kTag, "Ethernet link down");
return;
}
if (event_id == ETHERNET_EVENT_START) {
ESP_LOGI(kTag, "Ethernet driver started");
return;
}
if (event_id == ETHERNET_EVENT_STOP) {
runtime_.clearEthernetIp();
ESP_LOGI(kTag, "Ethernet driver stopped");
}
return;
}
if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP && event_data != nullptr) {
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
char ip[16] = {0};
esp_ip4addr_ntoa(&event->ip_info.ip, ip, sizeof(ip));
EthernetInfo info = runtime_.deviceInfo().eth.value_or(EthernetInfo{});
uint8_t mac[6] = {};
if (eth_handle_ != nullptr && esp_eth_ioctl(eth_handle_, ETH_CMD_G_MAC_ADDR, mac) == ESP_OK) {
info.mac = MacToHex(mac);
}
info.ip = ip;
runtime_.setEthernetInfo(std::move(info));
ESP_LOGI(kTag, "Ethernet got IP %s", ip);
}
}
void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t event_id,
void* event_data) {
if (!config_.wifi_enabled) {
@@ -1168,6 +1477,15 @@ std::string GatewayNetworkService::deviceInfoJson() const {
}
}
if (info.eth.has_value()) {
cJSON* eth = cJSON_CreateObject();
if (eth != nullptr) {
cJSON_AddStringToObject(eth, "mac", info.eth->mac.c_str());
cJSON_AddStringToObject(eth, "IP", info.eth->ip.c_str());
cJSON_AddItemToObject(root, "ethInfo", eth);
}
}
const std::string rendered = PrintJson(root);
cJSON_Delete(root);
return rendered;