diff --git a/README.md b/README.md index d1da95f..e05979c 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,14 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway. - `gateway_bacnet/`: BACnet/IP server adapter backed by bacnet-stack, including the gateway-owned BACnet bridge model adapter. - `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications. - `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out. - - `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, and BOOT-button Wi-Fi reset for the native gateway. + - `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, W5500 SPI Ethernet startup/teardown, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, and BOOT-button Wi-Fi reset for the native gateway. - `gateway_runtime/`: persistent runtime state, command queueing, and device info services. - `gateway_485_control/`: optional 485 Lua control bridge for framed `0x28 0x01` commands and `0x22 ... checksum` notifications at `9600 8N1`; disabled by default because UART0 must be moved off the ESP-IDF console first. - `gateway_usb_setup/`: optional USB Serial/JTAG setup bridge; disabled by default so USB remains available for debug at boot. ## Current status -The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, and BOOT-button Wi-Fi credential clearing, and an optional `gateway_485_control` bridge that claims UART0 for Lua-compatible framed command ingress plus `0x22` notification egress when the console is moved off UART0. Startup behavior is configured in `main/Kconfig.projbuild`: BLE is enabled by default, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected, and the UART0 control bridge stays disabled unless the deployment explicitly repurposes UART0 away from the ESP-IDF console. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots. +The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, W5500 SPI Ethernet with DHCP, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, and BOOT-button Wi-Fi credential clearing, and an optional `gateway_485_control` bridge that claims UART0 for Lua-compatible framed command ingress plus `0x22` notification egress when the console is moved off UART0. Startup behavior is configured in `main/Kconfig.projbuild`: BLE and wired Ethernet are enabled by default, W5500 initialization and startup probe failures are ignored by default for boards without populated Ethernet hardware by fully disabling Ethernet for that boot, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected, and the UART0 control bridge stays disabled unless the deployment explicitly repurposes UART0 away from the ESP-IDF console. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots. ## Modbus diff --git a/apps/gateway/main/Kconfig.projbuild b/apps/gateway/main/Kconfig.projbuild index d1b58c7..a512424 100644 --- a/apps/gateway/main/Kconfig.projbuild +++ b/apps/gateway/main/Kconfig.projbuild @@ -380,6 +380,109 @@ config GATEWAY_SMARTCONFIG_TIMEOUT_SEC help Timeout passed to ESP-IDF smartconfig before provisioning restarts internally. +config GATEWAY_ETHERNET_SUPPORTED + bool "Wired Ethernet gateway transport is supported" + default y + select ETH_USE_SPI_ETHERNET + select ETH_SPI_ETHERNET_W5500 + help + Enables the ESP-IDF Ethernet driver path for wired gateway networking. The + native gateway currently provisions a W5500 SPI Ethernet controller and + exposes the same HTTP, UDP, KNXnet/IP, BACnet/IP, Modbus TCP, and cloud + services over the wired netif. + +config GATEWAY_START_ETHERNET_ENABLED + bool "Start wired Ethernet at startup" + depends on GATEWAY_ETHERNET_SUPPORTED + default y + help + Starts the configured W5500 Ethernet netif during boot and uses DHCP for + address assignment. Disable this when the board is built without Ethernet + hardware even though the firmware keeps Ethernet support compiled in. + +config GATEWAY_ETHERNET_IGNORE_INIT_FAILURE + bool "Ignore wired Ethernet init failures" + depends on GATEWAY_START_ETHERNET_ENABLED + default y + help + Continues booting if the W5500 Ethernet controller is missing, held in + reset, miswired, or otherwise fails ESP-IDF Ethernet driver startup. + Disable this for strict hardware bring-up where Ethernet failure should + abort application startup. + +menu "Gateway Wired Ethernet" + depends on GATEWAY_ETHERNET_SUPPORTED + +config GATEWAY_ETHERNET_W5500_SPI_HOST + int "W5500 SPI host number" + range 1 2 + default 1 + help + SPI host used for the W5500 Ethernet controller. On ESP32-S3, host 1 maps + to SPI2 and host 2 maps to SPI3; do not use host 0 because it is reserved + by flash/PSRAM. + +config GATEWAY_ETHERNET_W5500_SCLK_GPIO + int "W5500 SPI SCLK GPIO" + range 0 48 + default 14 + +config GATEWAY_ETHERNET_W5500_MOSI_GPIO + int "W5500 SPI MOSI GPIO" + range 0 48 + default 13 + +config GATEWAY_ETHERNET_W5500_MISO_GPIO + int "W5500 SPI MISO GPIO" + range 0 48 + default 12 + +config GATEWAY_ETHERNET_W5500_CS_GPIO + int "W5500 SPI CS GPIO" + range 0 48 + default 15 + +config GATEWAY_ETHERNET_W5500_INT_GPIO + int "W5500 interrupt GPIO" + range -1 48 + default 4 + help + W5500 interrupt pin. Set to -1 to disable interrupt mode and poll RX + status periodically. + +config GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS + int "W5500 polling period ms" + range 0 1000 + default 0 + help + Polling interval used when the W5500 interrupt pin is disabled. A value of + 0 keeps interrupt mode when an interrupt GPIO is configured; if the + interrupt GPIO is -1, the gateway falls back to 100 ms polling. + +config GATEWAY_ETHERNET_W5500_CLOCK_MHZ + int "W5500 SPI clock MHz" + range 5 80 + default 36 + +config GATEWAY_ETHERNET_PHY_RESET_GPIO + int "Ethernet PHY reset GPIO" + range -1 48 + default 5 + help + GPIO used to reset the W5500 PHY. Set to -1 to disable hardware reset. + +config GATEWAY_ETHERNET_PHY_ADDR + int "Ethernet PHY address" + range 0 31 + default 1 + +config GATEWAY_ETHERNET_RX_TASK_STACK_SIZE + int "Ethernet RX task stack bytes" + range 2048 8192 + default 3072 + +endmenu + config GATEWAY_BRIDGE_SUPPORTED bool "dali_cpp bridge runtime is supported" default y @@ -411,7 +514,7 @@ choice GATEWAY_MODBUS_DEFAULT_TRANSPORT config GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP bool "TCP server" - depends on GATEWAY_WIFI_SUPPORTED + depends on GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED config GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU bool "RTU server on UART" @@ -488,7 +591,7 @@ config GATEWAY_MODBUS_SERIAL_RS485_DE_PIN config GATEWAY_BACNET_BRIDGE_SUPPORTED bool "BACnet/IP bridge is supported" - depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED + depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED) default n help Enables BACnet bridge configuration, binding discovery, and the bacnet-stack BACnet/IP server adapter. @@ -503,7 +606,7 @@ config GATEWAY_START_BACNET_BRIDGE_ENABLED config GATEWAY_KNX_BRIDGE_SUPPORTED bool "KNX to DALI bridge is supported" - depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED + depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED) default n help Enables the gateway-owned KNX group-address router and KNXnet/IP TP/IP @@ -595,7 +698,7 @@ config GATEWAY_BRIDGE_KNX_TASK_PRIORITY config GATEWAY_CLOUD_BRIDGE_SUPPORTED bool "MQTT cloud bridge is supported" - depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED + depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED) default y help Enables per-channel DaliCloudBridge provisioning and MQTT downlink execution. diff --git a/apps/gateway/main/app_main.cpp b/apps/gateway/main/app_main.cpp index fabb509..4b580b7 100644 --- a/apps/gateway/main/app_main.cpp +++ b/apps/gateway/main/app_main.cpp @@ -96,6 +96,54 @@ #define CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC 60 #endif +#ifndef CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE +#define CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE 0 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST +#define CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST 1 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO +#define CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO 14 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO +#define CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO 13 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO +#define CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO 12 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO +#define CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO 15 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO +#define CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO 4 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS +#define CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS 0 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ +#define CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ 36 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO +#define CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO 5 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_PHY_ADDR +#define CONFIG_GATEWAY_ETHERNET_PHY_ADDR 1 +#endif + +#ifndef CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE +#define CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE 3072 +#endif + #ifndef CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE #define CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE 6144 #endif @@ -199,12 +247,24 @@ constexpr bool kWifiSupported = true; constexpr bool kWifiSupported = false; #endif +#ifdef CONFIG_GATEWAY_ETHERNET_SUPPORTED +constexpr bool kEthernetSupported = true; +#else +constexpr bool kEthernetSupported = false; +#endif + #ifdef CONFIG_GATEWAY_START_WIFI_STA_ENABLED constexpr bool kWifiStartupEnabled = true; #else constexpr bool kWifiStartupEnabled = false; #endif +#ifdef CONFIG_GATEWAY_START_ETHERNET_ENABLED +constexpr bool kEthernetStartupEnabled = true; +#else +constexpr bool kEthernetStartupEnabled = false; +#endif + #ifdef CONFIG_GATEWAY_BLE_SUPPORTED constexpr bool kBleSupported = true; #else @@ -642,7 +702,7 @@ extern "C" void app_main(void) { "gateway", kWifiSupported, kBleSupported, - true, + kEthernetSupported, kEspnowSetupSupported, kUsbSetupStartupEnabled, }; @@ -678,10 +738,11 @@ extern "C" void app_main(void) { ESP_ERROR_CHECK(s_cache->start()); gateway::GatewayControllerConfig controller_config; + const bool network_transport_supported = profile.enable_wifi || profile.enable_eth; controller_config.setup_supported = true; controller_config.ble_supported = profile.enable_ble; controller_config.wifi_supported = profile.enable_wifi; - controller_config.ip_router_supported = profile.enable_wifi || profile.enable_eth; + controller_config.ip_router_supported = network_transport_supported; controller_config.internal_scene_supported = true; controller_config.internal_group_supported = true; @@ -715,14 +776,14 @@ extern "C" void app_main(void) { bridge_config.bridge_enabled = true; bridge_config.modbus_enabled = kModbusBridgeSupported; bridge_config.modbus_startup_enabled = kModbusBridgeSupported && kModbusBridgeStartupEnabled; - bridge_config.bacnet_enabled = profile.enable_wifi && kBacnetBridgeSupported; - bridge_config.bacnet_startup_enabled = profile.enable_wifi && kBacnetBridgeSupported && + bridge_config.bacnet_enabled = network_transport_supported && kBacnetBridgeSupported; + bridge_config.bacnet_startup_enabled = network_transport_supported && kBacnetBridgeSupported && kBacnetBridgeStartupEnabled; - bridge_config.knx_enabled = profile.enable_wifi && kKnxBridgeSupported; - bridge_config.knx_startup_enabled = profile.enable_wifi && kKnxBridgeSupported && + bridge_config.knx_enabled = network_transport_supported && kKnxBridgeSupported; + bridge_config.knx_startup_enabled = network_transport_supported && kKnxBridgeSupported && kKnxBridgeStartupEnabled; - bridge_config.cloud_enabled = profile.enable_wifi && kCloudBridgeSupported; - bridge_config.cloud_startup_enabled = profile.enable_wifi && kCloudBridgeSupported && + bridge_config.cloud_enabled = network_transport_supported && kCloudBridgeSupported; + bridge_config.cloud_startup_enabled = network_transport_supported && kCloudBridgeSupported && kCloudBridgeStartupEnabled; bridge_config.modbus_task_stack_size = static_cast(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE); @@ -796,6 +857,12 @@ extern "C" void app_main(void) { if (profile.enable_wifi || profile.enable_eth) { gateway::GatewayNetworkServiceConfig network_config; network_config.wifi_enabled = profile.enable_wifi && kWifiStartupEnabled; + network_config.ethernet_enabled = profile.enable_eth && kEthernetStartupEnabled; + #if CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE + network_config.ethernet_ignore_init_failure = true; + #else + network_config.ethernet_ignore_init_failure = false; + #endif network_config.espnow_setup_enabled = profile.enable_espnow; network_config.espnow_setup_startup_enabled = profile.enable_espnow && kEspnowSetupStartupEnabled; @@ -815,6 +882,20 @@ extern "C" void app_main(void) { #endif network_config.http_port = static_cast(CONFIG_GATEWAY_NETWORK_HTTP_PORT); network_config.udp_port = static_cast(CONFIG_GATEWAY_NETWORK_UDP_PORT); + network_config.ethernet_spi_host = CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST; + network_config.ethernet_spi_sclk_gpio = CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO; + network_config.ethernet_spi_mosi_gpio = CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO; + network_config.ethernet_spi_miso_gpio = CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO; + network_config.ethernet_spi_cs_gpio = CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO; + network_config.ethernet_spi_int_gpio = CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO; + network_config.ethernet_poll_period_ms = + static_cast(CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS); + network_config.ethernet_spi_clock_mhz = + static_cast(CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ); + network_config.ethernet_phy_reset_gpio = CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO; + network_config.ethernet_phy_addr = CONFIG_GATEWAY_ETHERNET_PHY_ADDR; + network_config.ethernet_rx_task_stack_size = + static_cast(CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE); network_config.status_led_gpio = CONFIG_GATEWAY_STATUS_LED_GPIO; network_config.boot_button_gpio = CONFIG_GATEWAY_BOOT_BUTTON_GPIO; network_config.boot_button_long_press_ms = CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS; diff --git a/apps/gateway/sdkconfig b/apps/gateway/sdkconfig index f094324..59a32a8 100644 --- a/apps/gateway/sdkconfig +++ b/apps/gateway/sdkconfig @@ -656,6 +656,26 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y # CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set # CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60 +CONFIG_GATEWAY_ETHERNET_SUPPORTED=y +CONFIG_GATEWAY_START_ETHERNET_ENABLED=y +CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y + +# +# Gateway Wired Ethernet +# +CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=1 +CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=14 +CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=13 +CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=12 +CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=15 +CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=4 +CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0 +CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=36 +CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=5 +CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1 +CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=3072 +# end of Gateway Wired Ethernet + CONFIG_GATEWAY_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y # CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set diff --git a/apps/gateway/sdkconfig.defaults b/apps/gateway/sdkconfig.defaults index 4b149cb..0397ed7 100644 --- a/apps/gateway/sdkconfig.defaults +++ b/apps/gateway/sdkconfig.defaults @@ -12,4 +12,7 @@ CONFIG_BT_NIMBLE_ENABLED=y CONFIG_ETH_ENABLED=y CONFIG_ETH_USE_SPI_ETHERNET=y -CONFIG_ETH_SPI_ETHERNET_W5500=y \ No newline at end of file +CONFIG_ETH_SPI_ETHERNET_W5500=y +CONFIG_GATEWAY_ETHERNET_SUPPORTED=y +CONFIG_GATEWAY_START_ETHERNET_ENABLED=y +CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y \ No newline at end of file diff --git a/components/gateway_network/CMakeLists.txt b/components/gateway_network/CMakeLists.txt index 009e150..f0a5d8c 100644 --- a/components/gateway_network/CMakeLists.txt +++ b/components/gateway_network/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/components/gateway_network/include/gateway_network.hpp b/components/gateway_network/include/gateway_network.hpp index f31ec5a..80ec218 100644 --- a/components/gateway_network/include/gateway_network.hpp +++ b/components/gateway_network/include/gateway_network.hpp @@ -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& 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& 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}; diff --git a/components/gateway_network/src/gateway_network.cpp b/components/gateway_network/src/gateway_network.cpp index 52d2169..c7e1146 100644 --- a/components/gateway_network/src/gateway_network.cpp +++ b/components/gateway_network/src/gateway_network.cpp @@ -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(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(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(ð_config, ð_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(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(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(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; diff --git a/components/gateway_runtime/include/gateway_runtime.hpp b/components/gateway_runtime/include/gateway_runtime.hpp index 6c04605..d0cb382 100644 --- a/components/gateway_runtime/include/gateway_runtime.hpp +++ b/components/gateway_runtime/include/gateway_runtime.hpp @@ -30,6 +30,11 @@ struct WirelessInfo { std::string ip; }; +struct EthernetInfo { + std::string mac; + std::string ip; +}; + struct GatewayRuntimeConfig { std::string_view project_name; std::string_view version; @@ -47,6 +52,7 @@ struct GatewayDeviceInfo { size_t dali_gateway_count{0}; bool ble_enabled{false}; std::optional wlan; + std::optional eth; }; class GatewaySettingsStore { @@ -119,6 +125,9 @@ class GatewayRuntime { void setGatewayCount(size_t gateway_count); void setWirelessInfo(WirelessInfo info); bool clearWirelessInfo(); + void setEthernetInfo(EthernetInfo info); + void clearEthernetInfo(); + void clearEthernetIp(); void setCommandAddressResolver(std::function resolver); GatewayDeviceInfo deviceInfo() const; @@ -159,6 +168,7 @@ class GatewayRuntime { CommandDropReason last_enqueue_drop_reason_{CommandDropReason::kNone}; std::function command_address_resolver_; std::optional wireless_info_; + std::optional ethernet_info_; SemaphoreHandle_t command_lock_{nullptr}; }; diff --git a/components/gateway_runtime/src/gateway_runtime.cpp b/components/gateway_runtime/src/gateway_runtime.cpp index 026af3f..6a2ef6c 100644 --- a/components/gateway_runtime/src/gateway_runtime.cpp +++ b/components/gateway_runtime/src/gateway_runtime.cpp @@ -450,6 +450,23 @@ bool GatewayRuntime::clearWirelessInfo() { return settings_.clearWifiCredentials(); } +void GatewayRuntime::setEthernetInfo(EthernetInfo info) { + LockGuard guard(command_lock_); + ethernet_info_ = std::move(info); +} + +void GatewayRuntime::clearEthernetInfo() { + LockGuard guard(command_lock_); + ethernet_info_.reset(); +} + +void GatewayRuntime::clearEthernetIp() { + LockGuard guard(command_lock_); + if (ethernet_info_.has_value()) { + ethernet_info_->ip.clear(); + } +} + void GatewayRuntime::setCommandAddressResolver( std::function resolver) { LockGuard guard(command_lock_); @@ -470,6 +487,7 @@ GatewayDeviceInfo GatewayRuntime::deviceInfo() const { info.dali_gateway_count = gateway_count_; info.ble_enabled = ble_enabled_; info.wlan = wireless_info_; + info.eth = ethernet_info_; return info; }