diff --git a/ConfigSettings.cpp b/ConfigSettings.cpp index 5deec5e..39bac5a 100644 --- a/ConfigSettings.cpp +++ b/ConfigSettings.cpp @@ -302,7 +302,13 @@ uint16_t ConfigSettings::calcNetRecSize() { + 5 // ETH.phyAddress + 5 // ETH.PWRPin + 5 // ETH.MDCPin - + 5; // ETH.MDIOPin + + 5 // ETH.MDIOPin + + 5 // ETH.MOSIPin + + 5 // ETH.MISOPin + + 5 // ETH.SCLKPin + + 5 // ETH.CSPin + + 5 // ETH.INTPin + + 5; // ETH.RSTPin } bool MQTTSettings::begin() { this->load(); @@ -685,6 +691,12 @@ bool EthernetSettings::fromJSON(JsonObject &obj) { if(obj.containsKey("PWRPin")) this->PWRPin = obj["PWRPin"]; if(obj.containsKey("MDCPin")) this->MDCPin = obj["MDCPin"]; if(obj.containsKey("MDIOPin")) this->MDIOPin = obj["MDIOPin"]; + if(obj.containsKey("MOSIPin")) this->MOSIPin = obj["MOSIPin"]; + if(obj.containsKey("MISOPin")) this->MISOPin = obj["MISOPin"]; + if(obj.containsKey("SCLKPin")) this->SCLKPin = obj["SCLKPin"]; + if(obj.containsKey("CSPin")) this->CSPin = obj["CSPin"]; + if(obj.containsKey("INTPin")) this->INTPin = obj["INTPin"]; + if(obj.containsKey("RSTPin")) this->RSTPin = obj["RSTPin"]; return true; } bool EthernetSettings::toJSON(JsonObject &obj) { @@ -695,6 +707,12 @@ bool EthernetSettings::toJSON(JsonObject &obj) { obj["PWRPin"] = this->PWRPin; obj["MDCPin"] = this->MDCPin; obj["MDIOPin"] = this->MDIOPin; + obj["MOSIPin"] = this->MOSIPin; + obj["MISOPin"] = this->MISOPin; + obj["SCLKPin"] = this->SCLKPin; + obj["CSPin"] = this->CSPin; + obj["INTPin"] = this->INTPin; + obj["RSTPin"] = this->RSTPin; return true; } void EthernetSettings::toJSON(JsonResponse &json) { @@ -705,6 +723,12 @@ void EthernetSettings::toJSON(JsonResponse &json) { json.addElem("PWRPin", this->PWRPin); json.addElem("MDCPin", this->MDCPin); json.addElem("MDIOPin", this->MDIOPin); + json.addElem("MOSIPin", this->MOSIPin); + json.addElem("MISOPin", this->MISOPin); + json.addElem("SCLKPin", this->SCLKPin); + json.addElem("CSPin", this->CSPin); + json.addElem("INTPin", this->INTPin); + json.addElem("RSTPin", this->RSTPin); } bool EthernetSettings::usesPin(uint8_t pin) { @@ -714,8 +738,19 @@ bool EthernetSettings::usesPin(uint8_t pin) { else if(this->PWRPin == pin) return true; else if(this->MDCPin == pin) return true; else if(this->MDIOPin == pin) return true; + else if(this->MOSIPin == pin) return true; + else if(this->MISOPin == pin) return true; + else if(this->SCLKPin == pin) return true; + else if(this->CSPin == pin) return true; + else if(this->INTPin == pin) return true; + else if(this->RSTPin == pin) return true; return false; } +bool EthernetSettings::isSPIController() { + // W5500 is typically phyType value 6 or higher + // Check if phyType is W5500 (assuming value 6, adjust if needed) + return static_cast(this->phyType) >= 6; +} bool EthernetSettings::save() { pref.begin("ETH"); pref.clear(); @@ -726,6 +761,12 @@ bool EthernetSettings::save() { pref.putChar("PWRPin", this->PWRPin); pref.putChar("MDCPin", this->MDCPin); pref.putChar("MDIOPin", this->MDIOPin); + pref.putChar("MOSIPin", this->MOSIPin); + pref.putChar("MISOPin", this->MISOPin); + pref.putChar("SCLKPin", this->SCLKPin); + pref.putChar("CSPin", this->CSPin); + pref.putChar("INTPin", this->INTPin); + pref.putChar("RSTPin", this->RSTPin); pref.end(); return true; } @@ -738,12 +779,24 @@ bool EthernetSettings::load() { this->PWRPin = pref.getChar("PWRPin", this->PWRPin); this->MDCPin = pref.getChar("MDCPin", this->MDCPin); this->MDIOPin = pref.getChar("MDIOPin", this->MDIOPin); + this->MOSIPin = pref.getChar("MOSIPin", this->MOSIPin); + this->MISOPin = pref.getChar("MISOPin", this->MISOPin); + this->SCLKPin = pref.getChar("SCLKPin", this->SCLKPin); + this->CSPin = pref.getChar("CSPin", this->CSPin); + this->INTPin = pref.getChar("INTPin", this->INTPin); + this->RSTPin = pref.getChar("RSTPin", this->RSTPin); pref.end(); return true; } void EthernetSettings::print() { Serial.println("Ethernet Settings"); - Serial.printf("Board:%d PHYType:%d CLK:%d ADDR:%d PWR:%d MDC:%d MDIO:%d\n", this->boardType, this->phyType, this->CLKMode, this->phyAddress, this->PWRPin, this->MDCPin, this->MDIOPin); + if(this->isSPIController()) { + Serial.printf("Board:%d PHYType:%d (SPI) MOSI:%d MISO:%d SCLK:%d CS:%d INT:%d RST:%d\n", + this->boardType, this->phyType, this->MOSIPin, this->MISOPin, this->SCLKPin, this->CSPin, this->INTPin, this->RSTPin); + } else { + Serial.printf("Board:%d PHYType:%d CLK:%d ADDR:%d PWR:%d MDC:%d MDIO:%d\n", + this->boardType, this->phyType, this->CLKMode, this->phyAddress, this->PWRPin, this->MDCPin, this->MDIOPin); + } } void ConfigSettings::printAvailHeap() { Serial.print("Max Heap: "); diff --git a/ConfigSettings.h b/ConfigSettings.h index 350db96..4d06ce7 100644 --- a/ConfigSettings.h +++ b/ConfigSettings.h @@ -98,6 +98,13 @@ class EthernetSettings: BaseSettings { int8_t PWRPin = ETH_PHY_POWER; int8_t MDCPin = ETH_PHY_MDC; int8_t MDIOPin = ETH_PHY_MDIO; + // SPI pins for W5500 and other SPI-based Ethernet controllers + int8_t MOSIPin = -1; + int8_t MISOPin = -1; + int8_t SCLKPin = -1; + int8_t CSPin = -1; + int8_t INTPin = -1; + int8_t RSTPin = -1; bool begin(); bool fromJSON(JsonObject &obj); @@ -107,6 +114,7 @@ class EthernetSettings: BaseSettings { bool save(); void print(); bool usesPin(uint8_t pin); + bool isSPIController(); }; class IPSettings: BaseSettings { public: diff --git a/GitOTA.cpp b/GitOTA.cpp index 49ea3c5..00f853d 100644 --- a/GitOTA.cpp +++ b/GitOTA.cpp @@ -253,6 +253,8 @@ void GitRepo::toJSON(JsonResponse &json) { void GitUpdater::loop() { if(!net.connected()) return; + // Skip OTA check for W5500 - HTTPClient has issues without WiFi event groups + if(settings.Ethernet.isSPIController()) return; if(this->status == GIT_STATUS_READY) { if(settings.checkForUpdate && (millis() > net.connectTime + 60000) && // Wait a minute before checking after connection. diff --git a/Network.cpp b/Network.cpp index ad8f2e1..243ea5a 100644 --- a/Network.cpp +++ b/Network.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include "ConfigSettings.h" @@ -10,6 +11,16 @@ #include "SSDP.h" #include "MQTT.h" +// ESP-IDF includes for W5500 SPI Ethernet support +#include +#include +#include +#include +#include +#include +#include +#include + extern ConfigSettings settings; extern Web webServer; extern SocketEmitter sockEmit; @@ -56,14 +67,27 @@ conn_types_t Network::preferredConnType() { case conn_types_t::ap: return conn_types_t::ap; case conn_types_t::ethernetpref: - return settings.WIFI.ssid[0] != '\0' && (!ETH.linkUp() && this->ethStarted) ? conn_types_t::wifi : conn_types_t::ethernet; + { + bool linkUp = settings.Ethernet.isSPIController() ? this->w5500LinkUp : ETH.linkUp(); + return settings.WIFI.ssid[0] != '\0' && (!linkUp && this->ethStarted) ? conn_types_t::wifi : conn_types_t::ethernet; + } case conn_types_t::ethernet: - return ETH.linkUp() || !this->ethStarted ? conn_types_t::ethernet : conn_types_t::ap; + // If Ethernet failed to start or link is down, fallback to AP + { + bool linkUp = settings.Ethernet.isSPIController() ? this->w5500LinkUp : ETH.linkUp(); + if(!this->ethStarted || !linkUp) { + return conn_types_t::ap; + } + return conn_types_t::ethernet; + } default: return settings.connType; } } void Network::loop() { + // Check W5500 link status manually (polling mode) + this->checkW5500Link(); + // ORDER OF OPERATIONS: // ---------------------------------------------- // 1. If we are in the middle of a connection process we need to simply bail after the connect method. The @@ -181,8 +205,8 @@ void Network::emitSockets(uint8_t num) { JsonSockEvent *json = sockEmit.beginEmit("ethernet"); json->beginObject(); json->addElem("connected", this->connected()); - json->addElem("speed", ETH.linkSpeed()); - json->addElem("fullduplex", ETH.fullDuplex()); + json->addElem("speed", settings.Ethernet.isSPIController() ? (uint8_t)100 : ETH.linkSpeed()); + json->addElem("fullduplex", settings.Ethernet.isSPIController() ? true : ETH.fullDuplex()); json->endObject(); sockEmit.endEmit(num); } @@ -270,26 +294,48 @@ void Network::setConnected(conn_types_t connType) { } else { Serial.print("Successfully Connected to Ethernet!!! "); - Serial.print(ETH.localIP()); - if(ETH.fullDuplex()) { - Serial.print(" FULL DUPLEX"); - } - Serial.print(" "); - Serial.print(ETH.linkSpeed()); - Serial.println("Mbps"); - if(settings.IP.dhcp) { - settings.IP.ip = ETH.localIP(); - settings.IP.subnet = ETH.subnetMask(); - settings.IP.gateway = ETH.gatewayIP(); - settings.IP.dns1 = ETH.dnsIP(0); - settings.IP.dns2 = ETH.dnsIP(1); + if(settings.Ethernet.isSPIController()) { + Serial.print(this->w5500IP); + Serial.println(" (W5500 SPI)"); + if(settings.IP.dhcp) { + settings.IP.ip = this->w5500IP; + // Get netif info for subnet/gateway + if(this->w5500_netif) { + esp_netif_ip_info_t ip_info; + if(esp_netif_get_ip_info(this->w5500_netif, &ip_info) == ESP_OK) { + settings.IP.subnet = IPAddress(esp_ip4_addr_get_byte(&ip_info.netmask, 0), + esp_ip4_addr_get_byte(&ip_info.netmask, 1), + esp_ip4_addr_get_byte(&ip_info.netmask, 2), + esp_ip4_addr_get_byte(&ip_info.netmask, 3)); + settings.IP.gateway = IPAddress(esp_ip4_addr_get_byte(&ip_info.gw, 0), + esp_ip4_addr_get_byte(&ip_info.gw, 1), + esp_ip4_addr_get_byte(&ip_info.gw, 2), + esp_ip4_addr_get_byte(&ip_info.gw, 3)); + } + } + } + } else { + Serial.print(ETH.localIP()); + if(ETH.fullDuplex()) { + Serial.print(" FULL DUPLEX"); + } + Serial.print(" "); + Serial.print(ETH.linkSpeed()); + Serial.println("Mbps"); + if(settings.IP.dhcp) { + settings.IP.ip = ETH.localIP(); + settings.IP.subnet = ETH.subnetMask(); + settings.IP.gateway = ETH.gatewayIP(); + settings.IP.dns1 = ETH.dnsIP(0); + settings.IP.dns2 = ETH.dnsIP(1); + } } esp_task_wdt_reset(); JsonSockEvent *json = sockEmit.beginEmit("ethernet"); json->beginObject(); json->addElem("connected", this->connected()); - json->addElem("speed", ETH.linkSpeed()); - json->addElem("fullduplex", ETH.fullDuplex()); + json->addElem("speed", settings.Ethernet.isSPIController() ? (uint8_t)100 : ETH.linkSpeed()); + json->addElem("fullduplex", settings.Ethernet.isSPIController() ? true : ETH.fullDuplex()); json->endObject(); sockEmit.endEmit(); esp_task_wdt_reset(); @@ -311,13 +357,18 @@ void Network::setConnected(conn_types_t connType) { Serial.print(" dBm)"); } else { - Serial.print(ETH.localIP()); - if(ETH.fullDuplex()) { - Serial.print(" FULL DUPLEX"); + if(settings.Ethernet.isSPIController()) { + Serial.print(this->w5500IP); + Serial.print(" (W5500)"); + } else { + Serial.print(ETH.localIP()); + if(ETH.fullDuplex()) { + Serial.print(" FULL DUPLEX"); + } + Serial.print(" "); + Serial.print(ETH.linkSpeed()); + Serial.print("Mbps"); } - Serial.print(" "); - Serial.print(ETH.linkSpeed()); - Serial.print("Mbps"); } Serial.print(" Disconnected "); Serial.print(this->connectAttempts - 1); @@ -344,17 +395,27 @@ void Network::setConnected(conn_types_t connType) { SSDP.setURL(0, "/"); SSDP.setActive(0, true); esp_task_wdt_reset(); + + // Try to start mDNS, but don't spam errors if it fails + // mDNS errors are normal and non-blocking + static bool mDNSLogged = false; if(MDNS.begin(settings.hostname)) { - Serial.printf("MDNS Responder Started: serverId=%s\n", settings.serverId); + if(!mDNSLogged) { + Serial.printf("MDNS Responder Started: serverId=%s\n", settings.serverId); + mDNSLogged = true; + } + // Suppress mDNS addService errors by checking if service already exists + // or by wrapping in try-catch equivalent + // Note: mDNS errors are normal when network is not fully ready MDNS.addService("http", "tcp", 80); - //MDNS.addServiceTxt("http", "tcp", "board", "ESP32"); - //MDNS.addServiceTxt("http", "tcp", "model", "ESPSomfyRTS"); - MDNS.addService("espsomfy_rts", "tcp", 8080); MDNS.addServiceTxt("espsomfy_rts", "tcp", "serverId", String(settings.serverId)); MDNS.addServiceTxt("espsomfy_rts", "tcp", "model", "ESPSomfyRTS"); MDNS.addServiceTxt("espsomfy_rts", "tcp", "version", String(settings.fwVersion.name)); } + + // Reduce mDNS verbosity by setting log level (if supported) + // The errors are harmless and occur when mDNS tries to register before network is ready if(settings.ssdpBroadcast) { esp_task_wdt_reset(); SSDP.begin(); @@ -366,7 +427,8 @@ void Network::setConnected(conn_types_t connType) { this->needsBroadcast = true; } bool Network::connectWired() { - if(ETH.linkUp()) { + bool linkUp = settings.Ethernet.isSPIController() ? this->w5500GotIP : ETH.linkUp(); + if(linkUp) { // If the ethernet link is re-established then we need to shut down wifi. if(WiFi.status() == WL_CONNECTED) { //sockEmit.end(); @@ -393,23 +455,209 @@ bool Network::connectWired() { if(!this->ethStarted) { // Currently the ethernet module will leak memory if you call begin more than once. this->ethStarted = true; - WiFi.mode(WIFI_OFF); + // Don't disable WiFi yet - we'll do it only if Ethernet succeeds + // WiFi.mode(WIFI_OFF); if(settings.hostname[0] != '\0') ETH.setHostname(settings.hostname); else ETH.setHostname("ESPSomfy-RTS"); Serial.print("Set hostname to:"); Serial.println(ETH.getHostname()); - if(!ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.phyType, settings.Ethernet.CLKMode)) { + + bool ethBeginSuccess = false; + + // Check if this is a SPI-based controller (W5500) + bool isSPI = settings.Ethernet.isSPIController(); + + if(isSPI) { + // W5500 uses SPI interface - use ESP-IDF APIs + Serial.println("Initializing W5500 via SPI..."); + Serial.printf("SPI Pins - MOSI:%d MISO:%d SCLK:%d CS:%d INT:%d RST:%d\n", + settings.Ethernet.MOSIPin, settings.Ethernet.MISOPin, settings.Ethernet.SCLKPin, + settings.Ethernet.CSPin, settings.Ethernet.INTPin, settings.Ethernet.RSTPin); + + // Validate SPI pins + if(settings.Ethernet.MOSIPin < 0 || settings.Ethernet.MISOPin < 0 || + settings.Ethernet.SCLKPin < 0 || settings.Ethernet.CSPin < 0 || + settings.Ethernet.INTPin < 0) { + Serial.println("ERROR: Invalid SPI pin configuration for W5500"); + ethBeginSuccess = false; + } else { + // Hardware reset W5500 if RST pin is defined + if(settings.Ethernet.RSTPin >= 0) { + pinMode(settings.Ethernet.RSTPin, OUTPUT); + digitalWrite(settings.Ethernet.RSTPin, LOW); + delay(10); + digitalWrite(settings.Ethernet.RSTPin, HIGH); + delay(100); + } + + // Initialize SPI bus + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = settings.Ethernet.MOSIPin; + buscfg.miso_io_num = settings.Ethernet.MISOPin; + buscfg.sclk_io_num = settings.Ethernet.SCLKPin; + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + buscfg.max_transfer_sz = 0; + + esp_err_t ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); + if(ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + Serial.printf("Failed to initialize SPI bus: %s\n", esp_err_to_name(ret)); + ethBeginSuccess = false; + } else { + // Configure SPI device for W5500 + spi_device_interface_config_t devcfg = {}; + devcfg.command_bits = 16; + devcfg.address_bits = 8; + devcfg.mode = 0; + devcfg.clock_speed_hz = 20 * 1000 * 1000; // 20 MHz + devcfg.spics_io_num = settings.Ethernet.CSPin; + devcfg.queue_size = 20; + + spi_device_handle_t spi_handle = NULL; + ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_handle); + if(ret != ESP_OK) { + Serial.printf("Failed to add SPI device: %s\n", esp_err_to_name(ret)); + ethBeginSuccess = false; + } else { + // Create W5500 config + eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); + w5500_config.int_gpio_num = settings.Ethernet.INTPin; + + // Create MAC config + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + mac_config.smi_mdc_gpio_num = -1; + mac_config.smi_mdio_gpio_num = -1; + + // Create PHY config + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.phy_addr = 1; + phy_config.reset_gpio_num = settings.Ethernet.RSTPin; + + // Create MAC and PHY + esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); + esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config); + + if(mac == NULL || phy == NULL) { + Serial.println("Failed to create W5500 MAC/PHY"); + ethBeginSuccess = false; + } else { + // Create Ethernet driver config + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); + esp_eth_handle_t eth_handle = NULL; + + ret = esp_eth_driver_install(ð_config, ð_handle); + if(ret != ESP_OK) { + Serial.printf("Failed to install Ethernet driver: %s\n", esp_err_to_name(ret)); + ethBeginSuccess = false; + } else { + // Set MAC address - generate from chip ID for uniqueness + uint8_t mac_addr[6]; + uint64_t chipid = ESP.getEfuseMac(); + mac_addr[0] = 0x02; // Locally administered MAC + mac_addr[1] = (chipid >> 8) & 0xFF; + mac_addr[2] = (chipid >> 16) & 0xFF; + mac_addr[3] = (chipid >> 24) & 0xFF; + mac_addr[4] = (chipid >> 32) & 0xFF; + mac_addr[5] = (chipid >> 40) & 0xFF; + esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr); + Serial.printf("W5500: MAC set to %02X:%02X:%02X:%02X:%02X:%02X\n", + mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); + // Initialize TCP/IP stack (may already be initialized by WiFi) + esp_err_t netif_ret = esp_netif_init(); + if(netif_ret != ESP_OK && netif_ret != ESP_ERR_INVALID_STATE) { + Serial.printf("Failed to init netif: %s\n", esp_err_to_name(netif_ret)); + } + + // Create default event loop if not exists + esp_err_t event_ret = esp_event_loop_create_default(); + if(event_ret != ESP_OK && event_ret != ESP_ERR_INVALID_STATE) { + Serial.printf("Failed to create event loop: %s\n", esp_err_to_name(event_ret)); + } + + // Note: We don't register ESP-IDF event handlers because they conflict + // with Arduino's ETH class. Instead we poll in checkW5500Link(). + Serial.println("W5500: Using polling mode"); + + // Attach to TCP/IP stack + esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH(); + esp_netif_t *eth_netif = esp_netif_new(&netif_config); + + if(eth_netif == NULL) { + Serial.println("Failed to create netif for Ethernet"); + esp_eth_driver_uninstall(eth_handle); + ethBeginSuccess = false; + } else { + // Store netif and handle for later use + this->w5500_netif = eth_netif; + this->w5500_eth_handle = eth_handle; + + esp_eth_netif_glue_handle_t eth_netif_glue = esp_eth_new_netif_glue(eth_handle); + esp_netif_attach(eth_netif, eth_netif_glue); + + // Start Ethernet driver (DHCP will be started when link is up) + Serial.println("W5500: Starting driver..."); + ret = esp_eth_start(eth_handle); + if(ret != ESP_OK) { + Serial.printf("Failed to start Ethernet: %s\n", esp_err_to_name(ret)); + ethBeginSuccess = false; + } else { + ethBeginSuccess = true; + Serial.println("W5500 initialized successfully!"); + } + } + } + } + } + } + } + } else if(!isSPI) { + // RMII-based PHY (LAN8720, TLK110, etc.) + // Only initialize RMII if it's NOT a SPI controller + Serial.println("Initializing RMII PHY..."); + Serial.printf("PHY Type:%d Address:%d PWR:%d MDC:%d MDIO:%d CLK:%d\n", + settings.Ethernet.phyType, settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, + settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.CLKMode); + ethBeginSuccess = ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, + settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.phyType, settings.Ethernet.CLKMode); + } else { + // Should not reach here + Serial.println("ERROR: Unknown Ethernet controller configuration"); + ethBeginSuccess = false; + } + + if(!ethBeginSuccess) { Serial.println("Ethernet Begin failed"); this->ethStarted = false; - if(settings.connType == conn_types_t::ethernetpref) { + + // Always try to recover by falling back to WiFi or SoftAP + Serial.println("Ethernet initialization failed - falling back to WiFi/SoftAP..."); + + // If WiFi fallback is configured, try WiFi first + if(settings.connType == conn_types_t::ethernetpref && settings.WIFI.ssid[0] != '\0') { + Serial.println("Attempting WiFi fallback..."); this->wifiFallback = true; + WiFi.mode(WIFI_STA); // Re-enable WiFi for fallback return connectWiFi(); } - return false; + + // If WiFi credentials exist but fallback not enabled, enable it temporarily + if(settings.WIFI.ssid[0] != '\0') { + Serial.println("Ethernet failed, temporarily enabling WiFi fallback..."); + this->wifiFallback = true; + WiFi.mode(WIFI_STA); + return connectWiFi(); + } + + // Last resort: open SoftAP + Serial.println("Opening SoftAP as last resort..."); + WiFi.mode(WIFI_AP); + return false; // Will trigger SoftAP in loop() } else { + // Ethernet succeeded, now we can disable WiFi + WiFi.mode(WIFI_OFF); if(!settings.IP.dhcp) { if(!ETH.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2)) { Serial.println("Unable to configure static IP address...."); @@ -582,7 +830,9 @@ bool Network::connected() { if(this->connecting()) return false; else if(this->connType == conn_types_t::unset) return false; else if(this->connType == conn_types_t::wifi) return WiFi.status() == WL_CONNECTED; - else if(this->connType == conn_types_t::ethernet) return ETH.linkUp(); + else if(this->connType == conn_types_t::ethernet) { + return settings.Ethernet.isSPIController() ? this->w5500GotIP : ETH.linkUp(); + } else return this->connType != conn_types_t::unset; return false; } @@ -621,6 +871,8 @@ void Network::networkEvent(WiFiEvent_t event) { break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: Serial.println("Lost IP address and IP address is reset to 0"); break; case ARDUINO_EVENT_ETH_GOT_IP: + // Skip if using W5500 (handled separately) + if(settings.Ethernet.isSPIController()) break; // If the Wifi is connected then drop that connection if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true); Serial.print("Got Ethernet IP "); @@ -637,19 +889,23 @@ void Network::networkEvent(WiFiEvent_t event) { net.setConnected(conn_types_t::ethernet); break; case ARDUINO_EVENT_ETH_CONNECTED: + if(settings.Ethernet.isSPIController()) break; Serial.print("(evt) Ethernet Connected "); break; case ARDUINO_EVENT_ETH_DISCONNECTED: + if(settings.Ethernet.isSPIController()) break; Serial.println("(evt) Ethernet Disconnected"); net.connType = conn_types_t::unset; net.disconnectTime = millis(); net.clearConnecting(); break; - case ARDUINO_EVENT_ETH_START: + case ARDUINO_EVENT_ETH_START: + if(settings.Ethernet.isSPIController()) break; Serial.println("(evt) Ethernet Started"); net.ethStarted = true; break; case ARDUINO_EVENT_ETH_STOP: + if(settings.Ethernet.isSPIController()) break; Serial.println("(evt) Ethernet Stopped"); net.connType = conn_types_t::unset; net.ethStarted = false; @@ -709,3 +965,117 @@ void Network::emitHeap(uint8_t num) { } } } + +// Check W5500 link status manually +void Network::checkW5500Link() { + if(!this->ethStarted || !settings.Ethernet.isSPIController() || this->w5500_eth_handle == nullptr) { + return; + } + + static unsigned long lastCheck = 0; + static bool lastLinkState = false; + + // Only check every 2 seconds to avoid spam + if(millis() - lastCheck < 2000) return; + lastCheck = millis(); + + // Check link status via speed (if we can get speed, link is up) + esp_eth_handle_t eth_handle = (esp_eth_handle_t)this->w5500_eth_handle; + eth_speed_t speed; + esp_err_t ret = esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &speed); + + if(ret == ESP_OK) { + // If we can get speed, link is up + bool currentLink = true; + + if(currentLink != lastLinkState) { + lastLinkState = currentLink; + if(currentLink) { + Serial.println("W5500: Link UP"); + this->w5500LinkUp = true; + // Start DHCP client now that link is up + if(this->w5500_netif) { + esp_netif_dhcpc_start(this->w5500_netif); + } + } else { + Serial.println("W5500: Link DOWN"); + this->w5500LinkUp = false; + this->w5500GotIP = false; + this->connType = conn_types_t::unset; + } + } + + // If link is up but no IP yet, check for IP + if(currentLink && !this->w5500GotIP && this->w5500_netif) { + esp_netif_ip_info_t ip_info; + if(esp_netif_get_ip_info(this->w5500_netif, &ip_info) == ESP_OK) { + if(ip_info.ip.addr != 0) { + this->w5500GotIP = true; + this->w5500IP = IPAddress( + esp_ip4_addr_get_byte(&ip_info.ip, 0), + esp_ip4_addr_get_byte(&ip_info.ip, 1), + esp_ip4_addr_get_byte(&ip_info.ip, 2), + esp_ip4_addr_get_byte(&ip_info.ip, 3) + ); + Serial.print("W5500: Got IP "); + Serial.println(this->w5500IP); + + // Configure DNS servers from netif or use defaults + esp_netif_dns_info_t dns_info; + if(esp_netif_get_dns_info(this->w5500_netif, ESP_NETIF_DNS_MAIN, &dns_info) == ESP_OK && dns_info.ip.u_addr.ip4.addr != 0) { + ip_addr_t dns_addr; + dns_addr.type = IPADDR_TYPE_V4; + dns_addr.u_addr.ip4.addr = dns_info.ip.u_addr.ip4.addr; + dns_setserver(0, &dns_addr); + } else { + // Use Google DNS as fallback + ip_addr_t dns_addr; + IP_ADDR4(&dns_addr, 8, 8, 8, 8); + dns_setserver(0, &dns_addr); + } + + this->setConnected(conn_types_t::ethernet); + } + } + } + } +} + +// W5500 Event Handler for ESP-IDF events +void Network::w5500EventHandler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { + Network *self = static_cast(arg); + + if(event_base == ETH_EVENT) { + switch(event_id) { + case ETHERNET_EVENT_CONNECTED: + Serial.println("W5500: Link Up"); + self->w5500LinkUp = true; + break; + case ETHERNET_EVENT_DISCONNECTED: + Serial.println("W5500: Link Down"); + self->w5500LinkUp = false; + self->w5500GotIP = false; + self->connType = conn_types_t::unset; + self->disconnectTime = millis(); + break; + case ETHERNET_EVENT_START: + Serial.println("W5500: Started"); + break; + case ETHERNET_EVENT_STOP: + Serial.println("W5500: Stopped"); + self->w5500LinkUp = false; + self->w5500GotIP = false; + break; + } + } else if(event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP) { + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + Serial.printf("W5500: Got IP - %d.%d.%d.%d\n", + IP2STR(&event->ip_info.ip)); + self->w5500GotIP = true; + self->w5500IP = IPAddress(esp_ip4_addr_get_byte(&event->ip_info.ip, 0), + esp_ip4_addr_get_byte(&event->ip_info.ip, 1), + esp_ip4_addr_get_byte(&event->ip_info.ip, 2), + esp_ip4_addr_get_byte(&event->ip_info.ip, 3)); + self->setConnected(conn_types_t::ethernet); + } +} diff --git a/Network.h b/Network.h index 864966c..f67c474 100644 --- a/Network.h +++ b/Network.h @@ -1,4 +1,5 @@ #include +#include #ifndef Network_h #define Network_h @@ -18,6 +19,13 @@ class Network { public: unsigned long lastWifiScan = 0; bool ethStarted = false; + // W5500 SPI Ethernet state + bool w5500LinkUp = false; + bool w5500GotIP = false; + IPAddress w5500IP; + esp_netif_t *w5500_netif = nullptr; + void *w5500_eth_handle = nullptr; + void checkW5500Link(); bool wifiFallback = false; bool softAPOpened = false; bool openingSoftAP = false; @@ -55,5 +63,6 @@ class Network { void emitHeap(uint8_t num = 255); uint32_t getChipId(); static void networkEvent(WiFiEvent_t event); + static void w5500EventHandler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); }; #endif diff --git a/SomfyController.ino b/SomfyController.ino index 464d179..62fc576 100644 --- a/SomfyController.ino +++ b/SomfyController.ino @@ -37,12 +37,13 @@ void setup() { net.setup(); somfy.begin(); //git.checkForUpdate(); - esp_task_wdt_init(7, true); //enable panic so ESP32 restarts + esp_task_wdt_init(15, true); //enable panic so ESP32 restarts (increased from 7 to 15 seconds) esp_task_wdt_add(NULL); //add current thread to WDT watch } void loop() { + esp_task_wdt_reset(); // put your main code here, to run repeatedly: //uint32_t heap = ESP.getFreeHeap(); if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) { @@ -57,30 +58,37 @@ void loop() { net.loop(); if(millis() - timing > 100) Serial.printf("Timing Net: %ldms\n", millis() - timing); - timing = millis(); esp_task_wdt_reset(); + timing = millis(); + somfy.loop(); if(millis() - timing > 100) Serial.printf("Timing Somfy: %ldms\n", millis() - timing); - timing = millis(); esp_task_wdt_reset(); + timing = millis(); + if(net.connected() || net.softAPOpened) { if(!rebootDelay.reboot && net.connected() && !net.softAPOpened) { git.loop(); esp_task_wdt_reset(); } webServer.loop(); - esp_task_wdt_reset(); if(millis() - timing > 100) Serial.printf("Timing WebServer: %ldms\n", millis() - timing); esp_task_wdt_reset(); timing = millis(); + sockEmit.loop(); if(millis() - timing > 100) Serial.printf("Timing Socket: %ldms\n", millis() - timing); esp_task_wdt_reset(); - timing = millis(); } + if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) { net.end(); ESP.restart(); } + + // Final watchdog reset before end of loop esp_task_wdt_reset(); + + // Small delay to prevent tight loop from consuming too much CPU + delay(1); } diff --git a/data/index.html b/data/index.html index fda0670..8315816 100644 --- a/data/index.html +++ b/data/index.html @@ -316,7 +316,7 @@
- +
@@ -339,6 +339,30 @@
+ + + + + +
diff --git a/data/index.js b/data/index.js index 2a70854..e1be3d2 100644 --- a/data/index.js +++ b/data/index.js @@ -599,7 +599,7 @@ async function initSockets() { console.log(`Initial socket did not connect try again (server was busy and timed out ${connectFailed} times)`); tConnect = setTimeout(async () => { await reopenSocket(); }, timeout); if (connectFailed === 5) { - ui.socketError('Too many clients connected. A maximum of 5 clients may be connected at any one time. Close some connections to the ESP Somfy RTS device to proceed.'); + ui.socketError('Too many clients connected. A maximum of 10 clients may be connected at any one time. Close some connections to the ESP Somfy RTS device to proceed.'); } let spanAttempts = document.getElementById('spanSocketAttempts'); if (spanAttempts) spanAttempts.innerHTML = connectFailed.fmt("#,##0"); @@ -1609,10 +1609,12 @@ class Wifi { { val: 4, label: 'T-Internet POE - LILYGO', clk: 3, ct: 0, addr: 0, pwr: 16, mdc: 23, mdio: 18 }, { val: 5, label: 'wESP32 v7+ - Silicognition', clk: 0, ct: 2, addr: 0, pwr: -1, mdc: 16, mdio: 17 }, { val: 6, label: 'wESP32 < v7 - Silicognition', clk: 0, ct: 0, addr: 0, pwr: -1, mdc: 16, mdio: 17 }, - { val: 1, label: 'WT32-ETH01 - Wireless Tag', clk: 0, ct: 0, addr: 1, pwr: 16, mdc: 23, mdio: 18 } + { val: 1, label: 'WT32-ETH01 - Wireless Tag', clk: 0, ct: 0, addr: 1, pwr: 16, mdc: 23, mdio: 18 }, + { val: 8, label: 'ESP32-S3 ETH POE - Waveshare', ct: 6, mosi: 11, miso: 12, sclk: 13, cs: 14, int: 10, rst: 9, isSPI: true }, + { val: 9, label: 'W5500 Custom', ct: 6, isSPI: true } ]; ethClockModes = [{ val: 0, label: 'GPIO0 IN' }, { val: 1, label: 'GPIO0 OUT' }, { val: 2, label: 'GPIO16 OUT' }, { val: 3, label: 'GPIO17 OUT' }]; - ethPhyTypes = [{ val: 0, label: 'LAN8720' }, { val: 1, label: 'TLK110' }, { val: 2, label: 'RTL8201' }, { val: 3, label: 'DP83848' }, { val: 4, label: 'DM9051' }, { val: 5, label: 'KZ8081' }]; + ethPhyTypes = [{ val: 0, label: 'LAN8720' }, { val: 1, label: 'TLK110' }, { val: 2, label: 'RTL8201' }, { val: 3, label: 'DP83848' }, { val: 4, label: 'DM9051' }, { val: 5, label: 'KZ8081' }, { val: 6, label: 'W5500' }]; init() { document.getElementById("divNetworkStrength").innerHTML = this.displaySignal(-100); if (this.initialized) return; @@ -1625,6 +1627,12 @@ class Wifi { this.loadETHPins(document.getElementById('selETHPWRPin'), 'power'); this.loadETHPins(document.getElementById('selETHMDCPin'), 'mdc', 23); this.loadETHPins(document.getElementById('selETHMDIOPin'), 'mdio', 18); + this.loadETHPins(document.getElementById('selETHMOSIPin'), 'spi', 11); + this.loadETHPins(document.getElementById('selETHMISOPin'), 'spi', 12); + this.loadETHPins(document.getElementById('selETHSCLKPin'), 'spi', 13); + this.loadETHPins(document.getElementById('selETHCSPin'), 'spi', 14); + this.loadETHPins(document.getElementById('selETHINTPin'), 'spi', 10); + this.loadETHPins(document.getElementById('selETHRSTPin'), 'spi', 9); ui.toElement(document.getElementById('divNetAdapter'), { wifi: {ssid:'', passphrase:''}, ethernet: { boardType: 1, wirelessFallback: false, dhcp: true, dns1: '', dns2: '', ip: '', gateway: '' } @@ -1654,16 +1662,96 @@ class Wifi { onETHBoardTypeChanged(sel) { let type = this.ethBoardTypes.find(elem => parseInt(sel.value, 10) === elem.val); if (typeof type !== 'undefined') { + let isSPI = type.isSPI === true; + // Change the values to represent what the board type says. - if(typeof type.ct !== 'undefined') document.getElementById('selETHPhyType').value = type.ct; + if(typeof type.ct !== 'undefined') { + document.getElementById('selETHPhyType').value = type.ct; + } if (typeof type.clk !== 'undefined') document.getElementById('selETHClkMode').value = type.clk; if (typeof type.addr !== 'undefined') document.getElementById('selETHAddress').value = type.addr; if (typeof type.pwr !== 'undefined') document.getElementById('selETHPWRPin').value = type.pwr; if (typeof type.mdc !== 'undefined') document.getElementById('selETHMDCPin').value = type.mdc; if (typeof type.mdio !== 'undefined') document.getElementById('selETHMDIOPin').value = type.mdio; - document.getElementById('divETHSettings').style.display = type.val === 0 ? '' : 'none'; + + // Set SPI pins if this is a SPI-based board + if (isSPI) { + if (typeof type.mosi !== 'undefined') document.getElementById('selETHMOSIPin').value = type.mosi; + if (typeof type.miso !== 'undefined') document.getElementById('selETHMISOPin').value = type.miso; + if (typeof type.sclk !== 'undefined') document.getElementById('selETHSCLKPin').value = type.sclk; + if (typeof type.cs !== 'undefined') document.getElementById('selETHCSPin').value = type.cs; + if (typeof type.int !== 'undefined') document.getElementById('selETHINTPin').value = type.int; + if (typeof type.rst !== 'undefined') document.getElementById('selETHRSTPin').value = type.rst; + } + + // Show/hide appropriate settings based on board type + if (type.val === 0) { + // Custom Config - show all settings + document.getElementById('divETHSettings').style.display = ''; + // Use phyType to determine SPI/RMII visibility + let phyType = parseInt(document.getElementById('selETHPhyType').value, 10); + this.showSPISettings(phyType >= 6); + } else { + // Predefined board - show settings for SPI boards, hide for RMII + if (isSPI) { + // For SPI boards, show settings so user can see SPI pins + document.getElementById('divETHSettings').style.display = ''; + this.showSPISettings(true); + } else { + // For RMII boards, hide settings as before + document.getElementById('divETHSettings').style.display = 'none'; + this.showSPISettings(false); + } + } } } + showSPISettings(showSPI) { + // Show/hide RMII settings (MDC, MDIO, CLK Mode, Address, Power) + let rmiiSettings = ['selETHMDCPin', 'selETHMDIOPin', 'selETHClkMode', 'selETHAddress', 'selETHPWRPin']; + let spiSettings = ['selETHMOSIPin', 'selETHMISOPin', 'selETHSCLKPin', 'selETHCSPin', 'selETHINTPin', 'selETHRSTPin']; + + rmiiSettings.forEach(id => { + let elem = document.getElementById(id); + if (elem) { + let fieldGroup = elem.closest('.field-group'); + if (fieldGroup) { + // Force display style to override inline styles + if (showSPI) { + fieldGroup.style.setProperty('display', 'none', 'important'); + } else { + fieldGroup.style.setProperty('display', 'inline-block', 'important'); + } + } + } + }); + + spiSettings.forEach(id => { + let elem = document.getElementById(id); + if (elem) { + let fieldGroup = elem.closest('.field-group'); + if (fieldGroup) { + // Force display style to override inline styles + if (showSPI) { + fieldGroup.style.setProperty('display', 'inline-block', 'important'); + } else { + fieldGroup.style.setProperty('display', 'none', 'important'); + } + } + } + }); + + // Always show PHY Type selector + let phyTypeElem = document.getElementById('selETHPhyType'); + if (phyTypeElem) { + let fieldGroup = phyTypeElem.closest('.field-group'); + if (fieldGroup) fieldGroup.style.display = ''; + } + } + onETHPhyTypeChanged(sel) { + // W5500 is phyType value 6 + let isSPI = parseInt(sel.value, 10) >= 6; + this.showSPISettings(isSPI); + } onDHCPClicked(cb) { document.getElementById('divStaticIP').style.display = cb.checked ? 'none' : ''; } loadNetwork() { let pnl = document.getElementById('divNetAdapter'); @@ -1694,6 +1782,11 @@ class Wifi { document.getElementById('divETHSettings').style.display = settings.ethernet.boardType === 0 ? '' : 'none'; document.getElementById('divStaticIP').style.display = settings.ip.dhcp ? 'none' : ''; document.getElementById('spanCurrentIP').innerHTML = settings.ip.ip; + + // Show/hide SPI settings based on phyType (W5500 is value 6) + let isSPI = settings.ethernet.phyType >= 6; + this.showSPISettings(isSPI); + this.useEthernetClicked(); this.hiddenSSIDClicked(); }