ESPSomfy-RTS/Network.cpp
Anthony Marchand 0e0a482afb feat: ajout du support Ethernet W5500 SPI
- Intégration du contrôleur Ethernet W5500 via SPI pour les cartes
  comme Waveshare ESP32-S3 POE
- Configuration des pins SPI (MOSI, MISO, SCLK, CS, INT, RST) via
  l'interface web
- Utilisation directe des APIs ESP-IDF pour le W5500 car la classe
  Arduino ETH ne supporte pas nativement ce contrôleur
- Gestion manuelle du DHCP et DNS pour le W5500
- Protection des appels ETH Arduino quand W5500 est utilisé
- Désactivation temporaire de la vérification OTA GitHub pour W5500
  (problème de compatibilité HTTPClient)
- Ajout des presets de cartes dans l'interface (Waveshare, etc.)
2026-01-18 22:40:06 +01:00

1081 lines
42 KiB
C++

#include <ETH.h>
#include <WiFi.h>
#include <SPI.h>
#include <ESPmDNS.h>
#include <esp_task_wdt.h>
#include "ConfigSettings.h"
#include "Network.h"
#include "Web.h"
#include "Sockets.h"
#include "Utils.h"
#include "SSDP.h"
#include "MQTT.h"
// ESP-IDF includes for W5500 SPI Ethernet support
#include <esp_eth.h>
#include <esp_eth_mac.h>
#include <esp_eth_phy.h>
#include <esp_netif.h>
#include <esp_event.h>
#include <driver/spi_master.h>
#include <driver/gpio.h>
#include <lwip/dns.h>
extern ConfigSettings settings;
extern Web webServer;
extern SocketEmitter sockEmit;
extern MQTTClass mqtt;
extern rebootDelay_t rebootDelay;
extern Network net;
extern SomfyShadeController somfy;
static unsigned long _lastHeapEmit = 0;
static bool _apScanning = false;
static uint32_t _lastMaxHeap = 0;
static uint32_t _lastHeap = 0;
int connectRetries = 0;
void Network::end() {
SSDP.end();
mqtt.end();
sockEmit.end();
delay(100);
}
bool Network::setup() {
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
WiFi.persistent(false);
WiFi.setAutoReconnect(false);
WiFi.onEvent(this->networkEvent);
this->disconnectTime = millis();
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true, true);
if(settings.connType == conn_types_t::wifi || settings.connType == conn_types_t::unset) {
WiFi.persistent(false);
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
Serial.print("WiFi Mode: ");
Serial.println(WiFi.getMode());
WiFi.mode(WIFI_STA);
}
sockEmit.begin();
return true;
}
conn_types_t Network::preferredConnType() {
switch(settings.connType) {
case conn_types_t::wifi:
return settings.WIFI.ssid[0] != '\0' ? conn_types_t::wifi : conn_types_t::ap;
case conn_types_t::unset:
case conn_types_t::ap:
return conn_types_t::ap;
case conn_types_t::ethernetpref:
{
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:
// 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
// connect method will take care of our target connection for us.
// 2. Check to see what type of target connection we need.
// a. If this is an ethernet target then the connection needs to perform a fallback if applicable.
// b. If this is a wifi target then we need to first check to see if the SSID is available.
// c. If an SSID has not been set then we need to turn on the Soft AP.
// 3. If the Soft AP is open and the target is either wifi, ethernet, or ethernetpref then
// we need to shut it down if there are no connections and the preferred connection is available.
// a. Ethernet: Check for an active ethernet connection. We cannot rely on linkup because the PHY will
// report that the link is up when no IP address is being served.
// b. WiFi: Perform synchronous scan for APs related to the SSID. If the SSID can be found then perform
// the connection process for the WiFi connection.
// c. SoftAP: This condition retains the Soft AP because no other connection method is available.
conn_types_t ctype = this->preferredConnType();
this->connect(ctype); // Connection timeout handled in connect function as well as the opening of the Soft AP if needed.
if(this->connecting()) return; // If we are currently attempting to connect to something then we need to bail here.
if(_apScanning) {
if(settings.WIFI.hidden || // This user has elected to use a hidden AP.
(this->connected() && !settings.WIFI.roaming) || // We are already connected and should not be roaming.
(this->softAPOpened && WiFi.softAPgetStationNum() != 0) || // The Soft AP is open and a user is connected.
(ctype != conn_types_t::wifi)) { // The Ethernet link is up so we should ignore this scan.
Serial.println("Cancelling WiFi STA Scan...");
_apScanning = false;
WiFi.scanDelete();
}
else {
int16_t n = WiFi.scanComplete();
if( n >= 0) { // If the scan is complete but the WiFi isn't ready this can return 0.
uint8_t bssid[6];
int32_t channel = 0;
if(this->getStrongestAP(settings.WIFI.ssid, bssid, &channel)) {
if(!WiFi.BSSID() || memcmp(bssid, WiFi.BSSID(), sizeof(bssid)) != 0) {
if(!this->connected()) {
Serial.printf("Connecting to AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
this->connectWiFi(bssid, channel);
}
else {
Serial.printf("Found stronger AP %02X:%02X:%02X:%02X:%02X:%02X CH: %d\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], channel);
this->changeAP(bssid, channel);
}
}
}
_apScanning = false;
}
}
}
if(!this->connecting() && !settings.WIFI.hidden) {
if((this->softAPOpened && WiFi.softAPgetStationNum() == 0) ||
(!this->connected() && ctype == conn_types_t::wifi)) {
// If the Soft AP is opened and there are no clients connected then we need to scan for an AP. If
// our target exists we will exit out of the Soft AP and start that connection. We are also
// going to continuously scan when there is no connection and our preferred connection is wifi.
if(ctype == conn_types_t::wifi) {
// Scan for an AP but only if we are not already scanning.
if(!_apScanning && WiFi.scanNetworks(true, false, true, 300, 0, settings.WIFI.ssid) == -1) {
_apScanning = true;
}
}
}
else if(this->connected() && ctype == conn_types_t::wifi && settings.WIFI.roaming) {
// Periodically look for a roaming AP.
if(millis() > SSID_SCAN_INTERVAL + this->lastWifiScan) {
//Serial.println("Started scan for access points");
if(!_apScanning && WiFi.scanNetworks(true, false, true, 300, 0, settings.WIFI.ssid) == -1) {
_apScanning = true;
this->lastWifiScan = millis();
}
}
}
}
if(millis() - this->lastEmit > 1500) {
// Post our connection status if needed.
this->lastEmit = millis();
if(this->connected()) {
this->emitSockets();
this->lastEmit = millis();
}
esp_task_wdt_reset(); // Make sure we do not reboot here.
}
sockEmit.loop();
mqtt.loop();
if(settings.ssdpBroadcast && this->connected()) {
if(!SSDP.isStarted) SSDP.begin();
if(SSDP.isStarted) SSDP.loop();
}
else if(!settings.ssdpBroadcast && SSDP.isStarted) SSDP.end();
}
bool Network::changeAP(const uint8_t *bssid, const int32_t channel) {
esp_task_wdt_reset(); // Make sure we do not reboot here.
if(SSDP.isStarted) SSDP.end();
mqtt.disconnect();
//sockEmit.end();
WiFi.disconnect(false, true);
this->connType = conn_types_t::unset;
this->_connecting = true;
this->connectStart = millis();
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, channel, bssid);
this->connectStart = millis();
return false;
}
void Network::emitSockets() {
this->emitHeap();
if(this->needsBroadcast ||
(this->connType == conn_types_t::wifi && (abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 1 || WiFi.channel() != this->lastChannel))) {
this->emitSockets(255);
sockEmit.loop();
this->needsBroadcast = false;
}
}
void Network::emitSockets(uint8_t num) {
if(this->connType == conn_types_t::ethernet) {
JsonSockEvent *json = sockEmit.beginEmit("ethernet");
json->beginObject();
json->addElem("connected", this->connected());
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);
}
else {
if(WiFi.status() == WL_CONNECTED) {
JsonSockEvent *json = sockEmit.beginEmit("wifiStrength");
json->beginObject();
json->addElem("ssid", WiFi.SSID().c_str());
json->addElem("strength", (int32_t)WiFi.RSSI());
json->addElem("channel", (int32_t)this->channel);
json->endObject();
sockEmit.endEmit(num);
this->lastRSSI = WiFi.RSSI();
this->lastChannel = WiFi.channel();
}
else {
JsonSockEvent *json = sockEmit.beginEmit("wifiStrength");
json->beginObject();
json->addElem("ssid", "");
json->addElem("strength", (int8_t)-100);
json->addElem("channel", (int8_t)-1);
json->endObject();
sockEmit.endEmit(num);
json = sockEmit.beginEmit("ethernet");
json->beginObject();
json->addElem("connected", false);
json->addElem("speed", (uint8_t)0);
json->addElem("fullduplex", false);
json->endObject();
sockEmit.endEmit(num);
this->lastRSSI = -100;
this->lastChannel = -1;
}
}
this->emitHeap(num);
}
void Network::setConnected(conn_types_t connType) {
esp_task_wdt_reset();
this->connType = connType;
this->connectTime = millis();
connectRetries = 0;
Serial.println("Setting connected...");
if(this->connType == conn_types_t::wifi) {
if(this->softAPOpened && WiFi.softAPgetStationNum() == 0) {
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
}
this->_connecting = false;
this->ssid = WiFi.SSID();
this->mac = WiFi.BSSIDstr();
this->strength = WiFi.RSSI();
this->channel = WiFi.channel();
this->connectAttempts++;
}
else if(this->connType == conn_types_t::ethernet) {
if(this->softAPOpened) {
Serial.println("Disonnecting from SoftAP");
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
}
this->connectAttempts++;
this->_connecting = false;
this->wifiFallback = false;
}
// NET: Begin this in the startup.
//sockEmit.begin();
esp_task_wdt_reset();
if(this->connectAttempts == 1) {
Serial.println();
if(this->connType == conn_types_t::wifi) {
Serial.print("Successfully Connected to WiFi!!!!");
Serial.print(WiFi.localIP());
Serial.print(" (");
Serial.print(this->strength);
Serial.println("dbm)");
if(settings.IP.dhcp) {
settings.IP.ip = WiFi.localIP();
settings.IP.subnet = WiFi.subnetMask();
settings.IP.gateway = WiFi.gatewayIP();
settings.IP.dns1 = WiFi.dnsIP(0);
settings.IP.dns2 = WiFi.dnsIP(1);
}
}
else {
Serial.print("Successfully Connected to Ethernet!!! ");
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", 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();
}
}
else {
Serial.println();
Serial.print("Reconnected after ");
Serial.print(1.0 * (millis() - this->connectStart)/1000);
Serial.print("sec IP: ");
if(this->connType == conn_types_t::wifi) {
Serial.print(WiFi.localIP());
Serial.print(" ");
Serial.print(this->mac);
Serial.print(" CH:");
Serial.print(this->channel);
Serial.print(" (");
Serial.print(this->strength);
Serial.print(" dBm)");
}
else {
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(" Disconnected ");
Serial.print(this->connectAttempts - 1);
Serial.println(" times");
}
SSDP.setHTTPPort(80);
SSDP.setSchemaURL(0, "upnp.xml");
SSDP.setChipId(0, this->getChipId());
SSDP.setDeviceType(0, "urn:schemas-rstrouse-org:device:ESPSomfyRTS:1");
SSDP.setName(0, settings.hostname);
//SSDP.setSerialNumber(0, "C2496952-5610-47E6-A968-2FC19737A0DB");
//SSDP.setUUID(0, settings.uuid);
SSDP.setModelName(0, "ESPSomfy RTS");
if(strlen(settings.chipModel) == 0) SSDP.setModelNumber(0, "ESP32");
else {
char sModel[20] = "";
snprintf(sModel, sizeof(sModel), "ESP32-%s", settings.chipModel);
SSDP.setModelNumber(0, sModel);
}
SSDP.setModelURL(0, "https://github.com/rstrouse/ESPSomfy-RTS");
SSDP.setManufacturer(0, "rstrouse");
SSDP.setManufacturerURL(0, "https://github.com/rstrouse");
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)) {
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.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();
}
else if(SSDP.isStarted) SSDP.end();
esp_task_wdt_reset();
this->emitSockets();
settings.printAvailHeap();
this->needsBroadcast = true;
}
bool Network::connectWired() {
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();
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
if(this->connType != conn_types_t::ethernet) this->setConnected(conn_types_t::ethernet);
return true;
}
else if(this->ethStarted) {
// There is no wired connection so we need to fallback if appropriate.
if(settings.connType == conn_types_t::ethernetpref && settings.WIFI.ssid[0] != '\0')
return this->connectWiFi();
}
if(this->connectAttempts > 0) {
Serial.printf("Ethernet Connection Lost... %d Reconnecting ", this->connectAttempts);
Serial.println(this->mac);
}
else
Serial.println("Connecting to Wired Ethernet");
this->_connecting = true;
this->connTarget = conn_types_t::ethernet;
this->connType = conn_types_t::unset;
if(!this->ethStarted) {
// Currently the ethernet module will leak memory if you call begin more than once.
this->ethStarted = true;
// 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());
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(&eth_config, &eth_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;
// 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();
}
// 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....");
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
}
else
ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
}
this->connectStart = millis();
return true;
}
void Network::updateHostname() {
if(settings.hostname[0] != '\0' && this->connected()) {
if(this->connType == conn_types_t::ethernet &&
strcmp(settings.hostname, ETH.getHostname()) != 0) {
Serial.printf("Updating host name to %s...\n", settings.hostname);
ETH.setHostname(settings.hostname);
MDNS.setInstanceName(settings.hostname);
SSDP.setName(0, settings.hostname);
}
else if(strcmp(settings.hostname, WiFi.getHostname()) != 0) {
Serial.printf("Updating host name to %s...\n", settings.hostname);
WiFi.setHostname(settings.hostname);
MDNS.setInstanceName(settings.hostname);
SSDP.setName(0, settings.hostname);
}
}
}
bool Network::connectWiFi(const uint8_t *bssid, const int32_t channel) {
if(this->softAPOpened && WiFi.softAPgetStationNum() > 0) {
// There is a client connected to the soft AP. We do not want to close out the connection. While both the
// Soft AP and a wifi connection can coexist on ESP32 the performance is abysmal.
WiFi.disconnect(false);
this->_connecting = false;
this->connType = conn_types_t::unset;
return true;
}
WiFi.setSleep(false);
if(!settings.IP.dhcp) {
if(!WiFi.config(settings.IP.ip, settings.IP.gateway, settings.IP.subnet, settings.IP.dns1, settings.IP.dns2))
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
else
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE, INADDR_NONE);
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
delay(100);
if(bssid && channel > 0) {
if(WiFi.status() == WL_CONNECTED && WiFi.SSID().compareTo(settings.WIFI.ssid) == 0
&& WiFi.channel() == channel) {
this->disconnected = 0;
return true;
}
this->connTarget = conn_types_t::wifi;
this->connType = conn_types_t::unset;
Serial.println("WiFi begin...");
this->_connecting = true;
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, channel, bssid);
this->connectStart = millis();
}
else if(settings.WIFI.ssid[0] != '\0') {
if(WiFi.status() == WL_CONNECTED && WiFi.SSID().compareTo(settings.WIFI.ssid) == 0) {
// If we are connected to the target SSID then just return.
this->disconnected = 0;
this->_connecting = true;
return true;
}
if(this->_connecting) return true;
this->_connecting = true;
this->connTarget = conn_types_t::wifi;
this->connType = conn_types_t::unset;
if(this->connectAttempts > 0) {
Serial.print("Connection Lost...");
Serial.print(this->mac);
Serial.print(" CH:");
Serial.print(this->channel);
Serial.print(" (");
Serial.print(this->strength);
Serial.println("dbm) ");
}
else Serial.println("Connecting to AP");
delay(100);
// There is also another method simply called hostname() but this is legacy for esp8266.
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
Serial.print("Set hostname to:");
Serial.println(WiFi.getHostname());
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
uint8_t _bssid[6];
int32_t _channel = 0;
if(!settings.WIFI.hidden && this->getStrongestAP(settings.WIFI.ssid, _bssid, &_channel)) {
Serial.printf("Found strongest AP %02X:%02X:%02X:%02X:%02X:%02X CH:%d\n", _bssid[0], _bssid[1], _bssid[2], _bssid[3], _bssid[4], _bssid[5], _channel);
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase, _channel, _bssid);
}
else
// If the user has the hidden flag set just connect to whatever the AP gives us.
WiFi.begin(settings.WIFI.ssid, settings.WIFI.passphrase);
}
this->connectStart = millis();
return true;
}
bool Network::connect(conn_types_t ctype) {
esp_task_wdt_reset();
if(this->connecting()) return true;
if(this->disconnectTime == 0) this->disconnectTime = millis();
if(ctype == conn_types_t::ethernet && this->connType != conn_types_t::ethernet) {
// Here we need to call the connect to ethernet.
this->connectWired();
}
else if(ctype == conn_types_t::ap || (!this->connected() && millis() > this->disconnectTime + CONNECT_TIMEOUT)) {
if(!this->softAPOpened && !this->openingSoftAP) {
this->disconnectTime = millis();
this->openSoftAP();
}
else if(this->softAPOpened && !this->openingSoftAP &&
(ctype == conn_types_t::wifi && this->connType != conn_types_t::wifi && settings.WIFI.hidden)) {
// When thge softAP is open then we need to try to connect to wifi repeatedly if the user connects to a hidden SSID.
this->connectWiFi();
}
}
else if((ctype == conn_types_t::wifi && this->connType != conn_types_t::wifi && settings.WIFI.hidden)) {
this->connectWiFi();
}
return true;
}
uint32_t Network::getChipId() {
uint32_t chipId = 0;
uint64_t mac = ESP.getEfuseMac();
for(int i=0; i<17; i=i+8) {
chipId |= ((mac >> (40 - i)) & 0xff) << i;
}
return chipId;
}
bool Network::getStrongestAP(const char *ssid, uint8_t *bssid, int32_t *channel) {
// The new AP must be at least 10dbm greater.
int32_t strength = this->connected() ? WiFi.RSSI() + 10 : -127;
int32_t chan = -1;
memset(bssid, 0x00, 6);
esp_task_wdt_delete(NULL);
int16_t n = WiFi.scanComplete();
//int16_t n = this->connected() ? WiFi.scanComplete() : WiFi.scanNetworks(false, false, false, 300, 0, ssid);
esp_task_wdt_add(NULL);
for(int16_t i = 0; i < n; i++) {
if(WiFi.SSID(i).compareTo(ssid) == 0) {
if(WiFi.RSSI(i) > strength) {
strength = WiFi.RSSI(i);
memcpy(bssid, WiFi.BSSID(i), 6);
*channel = chan = WiFi.channel(i);
}
}
}
WiFi.scanDelete();
return chan > 0;
}
bool Network::openSoftAP() {
if(this->softAPOpened || this->openingSoftAP) return true;
if(this->connected()) WiFi.disconnect(false);
this->openingSoftAP = true;
Serial.println();
Serial.println("Turning the HotSpot On");
esp_task_wdt_reset(); // Make sure we do not reboot here.
WiFi.softAP(strlen(settings.hostname) > 0 ? settings.hostname : "ESPSomfy RTS", "");
delay(200);
return true;
}
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 settings.Ethernet.isSPIController() ? this->w5500GotIP : ETH.linkUp();
}
else return this->connType != conn_types_t::unset;
return false;
}
bool Network::connecting() {
if(this->_connecting && millis() > this->connectStart + CONNECT_TIMEOUT) this->_connecting = false;
return this->_connecting;
}
void Network::clearConnecting() { this->_connecting = false; }
void Network::networkEvent(WiFiEvent_t event) {
switch(event) {
case ARDUINO_EVENT_WIFI_READY: Serial.println("(evt) WiFi interface ready"); break;
case ARDUINO_EVENT_WIFI_SCAN_DONE:
Serial.printf("(evt) Completed scan for access points (%d)\n", WiFi.scanComplete());
//Serial.println("(evt) Completed scan for access points");
net.lastWifiScan = millis();
break;
case ARDUINO_EVENT_WIFI_STA_START:
Serial.println("WiFi station mode started");
if(settings.hostname[0] != '\0') WiFi.setHostname(settings.hostname);
break;
case ARDUINO_EVENT_WIFI_STA_STOP: Serial.println("(evt) WiFi clients stopped"); break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED: Serial.println("(evt) Connected to WiFi STA access point"); break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
Serial.printf("(evt) Disconnected from WiFi STA access point. Connecting: %d\n", net.connecting());
net.connType = conn_types_t::unset;
net.disconnectTime = millis();
net.clearConnecting();
break;
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: Serial.println("(evt) Authentication mode of STA access point has changed"); break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
Serial.print("(evt) Got WiFi STA IP: ");
Serial.println(WiFi.localIP());
net.connType = conn_types_t::wifi;
net.connectTime = millis();
net.setConnected(conn_types_t::wifi);
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 ");
Serial.println(ETH.localIP());
net.connectTime = millis();
net.connType = conn_types_t::ethernet;
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);
}
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:
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;
break;
case ARDUINO_EVENT_WIFI_AP_START:
Serial.print("(evt) WiFi SoftAP Started IP:");
Serial.println(WiFi.softAPIP());
net.openingSoftAP = false;
net.softAPOpened = true;
break;
case ARDUINO_EVENT_WIFI_AP_STOP:
if(!net.openingSoftAP) Serial.println("(evt) WiFi SoftAP Stopped");
net.softAPOpened = false;
break;
default:
if(event > ARDUINO_EVENT_ETH_START)
Serial.printf("(evt) Unknown Ethernet Event %d\n", event);
break;
}
}
void Network::emitHeap(uint8_t num) {
bool bEmit = false;
bool bTimeEmit = millis() - _lastHeapEmit > 15000;
bool bRoomEmit = false;
bool bValEmit = false;
if(num != 255 || this->needsBroadcast) bEmit = true;
if(millis() - _lastHeapEmit > 15000) bTimeEmit = true;
uint32_t freeHeap = ESP.getFreeHeap();
uint32_t maxHeap = ESP.getMaxAllocHeap();
uint32_t minHeap = ESP.getMinFreeHeap();
if(abs((int)(freeHeap - _lastHeap)) > 1500) bValEmit = true;
if(abs((int)(maxHeap - _lastMaxHeap)) > 1500) bValEmit = true;
bRoomEmit = sockEmit.activeClients(0) > 0;
if(bValEmit) bTimeEmit = millis() - _lastHeapEmit > 7000;
if(bEmit || bTimeEmit || bRoomEmit || bValEmit) {
JsonSockEvent *json = sockEmit.beginEmit("memStatus");
json->beginObject();
json->addElem("max", maxHeap);
json->addElem("free", freeHeap);
json->addElem("min", minHeap);
json->addElem("total", ESP.getHeapSize());
json->endObject();
if(num == 255 && bTimeEmit && bValEmit) {
sockEmit.endEmit(num);
_lastHeapEmit = millis();
_lastHeap = freeHeap;
_lastMaxHeap = maxHeap;
//Serial.printf("BROAD HEAP: Emit:%d TimeEmit:%d ValEmit:%d\n", bEmit, bTimeEmit, bValEmit);
}
else if(num != 255) {
sockEmit.endEmit(num);
//Serial.printf("TARGET HEAP %d: Emit:%d TimeEmit:%d ValEmit:%d\n", num, bEmit, bTimeEmit, bValEmit);
}
else if(bRoomEmit) {
sockEmit.endEmitRoom(0);
//Serial.printf("ROOM HEAP: Emit:%d TimeEmit:%d ValEmit:%d\n", bEmit, bTimeEmit, bValEmit);
}
}
}
// 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<Network*>(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);
}
}