feat: keep Ethernet settings independent of the framework

Prepares the move to Arduino 3.x / ESP-IDF 5.5, which renumbers
eth_phy_type_t (ETH_PHY_GENERIC is now 0) and only has the RMII MAC
API on the original ESP32.

- Store the PHY chip as our own id (eth_phy_id_t, the 2.x values) and
  translate it by name at ETH.begin(), so NVS, backups and the web UI
  keep their numbers on either framework.
- Build Ethernet only where CONFIG_ETH_USE_ESP32_EMAC is set instead of
  everywhere but the C6.  C3 and S3 could never use it.  A stored
  Ethernet connection falls back to Wi-Fi on those chips.
- /networksettings reports hasEthernet; the UI hides the Ethernet
  options without it.
- Fix the UI PHY list: 5 is the KSZ8041, add the KSZ8081 (6), drop the
  DM9051, an SPI chip the MAC can't drive.
- SSDP takes its address from WiFi/ETH.localIP() instead of the
  tcpip_adapter API IDF 5 removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cjkas 2026-10-19 21:13:49 +02:00
parent b74f382650
commit e1a62fe5be
12 changed files with 190 additions and 64 deletions

View file

@ -621,7 +621,7 @@ bool ShadeConfigFile::restoreFile(SomfyShadeController *s, const char *filename,
if(opts.network) {
settings.IP.save();
settings.WIFI.save();
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
settings.Ethernet.save();
#endif
}
@ -679,9 +679,9 @@ bool ShadeConfigFile::readNetRecord(restore_options_t &opts) {
if(opts.network) {
if(strncmp(settings.serverId, this->header.serverId, sizeof(settings.serverId)) == 0) {
ESP_LOGI(TAG, "Restoring Ethernet adapter settings");
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
settings.Ethernet.boardType = this->readUInt8(1);
settings.Ethernet.phyType = static_cast<eth_phy_type_t>(this->readUInt8(0));
settings.Ethernet.phyType = this->readUInt8(0);
settings.Ethernet.CLKMode = static_cast<eth_clock_mode_t>(this->readUInt8(0));
settings.Ethernet.phyAddress = this->readInt8(1);
settings.Ethernet.PWRPin = this->readInt8(1);
@ -1048,9 +1048,9 @@ bool ShadeConfigFile::writeNetRecord() {
this->writeBool(settings.MQTT.pubDisco);
this->writeVarString(settings.MQTT.rootTopic);
this->writeVarString(settings.MQTT.discoTopic);
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
this->writeUInt8(settings.Ethernet.boardType);
this->writeUInt8(static_cast<uint8_t>(settings.Ethernet.phyType));
this->writeUInt8(settings.Ethernet.phyType);
this->writeUInt8(static_cast<uint8_t>(settings.Ethernet.CLKMode));
this->writeInt8(settings.Ethernet.phyAddress);
this->writeInt8(settings.Ethernet.PWRPin);

View file

@ -181,7 +181,7 @@ bool ConfigSettings::begin() {
this->Security.begin();
this->IP.begin();
this->WIFI.begin();
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
this->Ethernet.begin();
#endif
this->NTP.begin();
@ -255,7 +255,7 @@ void ConfigSettings::print() {
ESP_LOGD(TAG, "Connection Type: %u", (unsigned int) this->connType);
this->NTP.print();
if(this->connType == conn_types_t::wifi || this->connType == conn_types_t::unset) this->WIFI.print();
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
if(this->connType == conn_types_t::ethernet || this->connType == conn_types_t::ethernetpref) this->Ethernet.print();
#endif
}
@ -601,7 +601,7 @@ bool WifiSettings::ssidExists(const char *ssid) {
}
return false;
}
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
EthernetSettings::EthernetSettings() {}
bool EthernetSettings::begin() {
this->load();
@ -611,7 +611,7 @@ bool EthernetSettings::fromJSON(JsonObject &obj) {
if(!obj["boardType"].isNull()) this->boardType = obj["boardType"];
if(!obj["phyAddress"].isNull()) this->phyAddress = obj["phyAddress"];
if(!obj["CLKMode"].isNull()) this->CLKMode = static_cast<eth_clock_mode_t>(obj["CLKMode"]);
if(!obj["phyType"].isNull()) this->phyType = static_cast<eth_phy_type_t>(obj["phyType"]);
if(!obj["phyType"].isNull()) this->phyType = obj["phyType"];
if(!obj["PWRPin"].isNull()) this->PWRPin = obj["PWRPin"];
if(!obj["MDCPin"].isNull()) this->MDCPin = obj["MDCPin"];
if(!obj["MDIOPin"].isNull()) this->MDIOPin = obj["MDIOPin"];
@ -621,7 +621,7 @@ bool EthernetSettings::toJSON(JsonObject &obj) {
obj["boardType"] = this->boardType;
obj["phyAddress"] = this->phyAddress;
obj["CLKMode"] = static_cast<uint8_t>(this->CLKMode);
obj["phyType"] = static_cast<uint8_t>(this->phyType);
obj["phyType"] = this->phyType;
obj["PWRPin"] = this->PWRPin;
obj["MDCPin"] = this->MDCPin;
obj["MDIOPin"] = this->MDIOPin;
@ -642,7 +642,7 @@ bool EthernetSettings::save() {
pref.clear();
pref.putChar("boardType", this->boardType);
pref.putChar("phyAddress", this->phyAddress);
pref.putChar("phyType", static_cast<uint8_t>(this->phyType));
pref.putChar("phyType", this->phyType);
pref.putChar("CLKMode", static_cast<uint8_t>(this->CLKMode));
pref.putChar("PWRPin", this->PWRPin);
pref.putChar("MDCPin", this->MDCPin);
@ -654,7 +654,7 @@ bool EthernetSettings::load() {
Preferences pref;
pref.begin("ETH");
this->boardType = pref.getChar("boardType", this->boardType);
this->phyType = static_cast<eth_phy_type_t>(pref.getChar("phyType", ETH_PHY_LAN8720));
this->phyType = pref.getChar("phyType", this->phyType);
this->CLKMode = static_cast<eth_clock_mode_t>(pref.getChar("CLKMode", ETH_CLOCK_GPIO0_IN));
this->phyAddress = pref.getChar("phyAddress", this->phyAddress);
this->PWRPin = pref.getChar("PWRPin", this->PWRPin);
@ -667,7 +667,7 @@ void EthernetSettings::print() {
ESP_LOGD(TAG, "Ethernet Settings Board:%d PHYType:%d CLK:%d ADDR:%d PWR:%d MDC:%d MDIO:%d",
this->boardType, this->phyType, this->CLKMode, this->phyAddress, this->PWRPin, this->MDCPin, this->MDIOPin);
}
#endif // CONFIG_IDF_TARGET_ESP32C6
#endif // HAS_ETHERNET
void ConfigSettings::printAvailHeap() {
ESP_LOGD(TAG, "Max Heap: %u", (unsigned int)ESP.getMaxAllocHeap());
ESP_LOGD(TAG, "Free Heap: %u", (unsigned int)ESP.getFreeHeap());

View file

@ -1,7 +1,5 @@
#include <ArduinoJson.h>
#ifndef CONFIG_IDF_TARGET_ESP32C6
#include <ETH.h>
#endif
#include "EthPhy.h"
#ifndef configsettings_h
#define configsettings_h
#include "WResp.h"
@ -94,17 +92,18 @@ class WifiSettings: BaseSettings {
void print();
};
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
class EthernetSettings: BaseSettings {
public:
EthernetSettings();
uint8_t boardType = 0; // These board types are enumerated in the ui and used to set the chip settings.
eth_phy_type_t phyType = ETH_PHY_LAN8720;
uint8_t phyType = static_cast<uint8_t>(eth_phy_id_t::LAN8720); // An eth_phy_id_t, see EthPhy.h.
eth_clock_mode_t CLKMode = ETH_CLOCK_GPIO0_IN;
int8_t phyAddress = ETH_PHY_ADDR;
int8_t PWRPin = ETH_PHY_POWER;
int8_t MDCPin = ETH_PHY_MDC;
int8_t MDIOPin = ETH_PHY_MDIO;
// The LAN8720 wiring Arduino 2.x defaulted to. 3.x has no defaults.
int8_t phyAddress = 0;
int8_t PWRPin = -1;
int8_t MDCPin = 23;
int8_t MDIOPin = 18;
bool begin();
bool fromJSON(JsonObject &obj);
@ -188,7 +187,7 @@ class ConfigSettings: BaseSettings {
uint8_t status;
IPSettings IP;
WifiSettings WIFI;
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
EthernetSettings Ethernet;
#endif
NTPSettings NTP;

View file

@ -1,6 +1,3 @@
#ifndef CONFIG_IDF_TARGET_ESP32C6
#include <ETH.h>
#endif
#include <WiFi.h>
#include <esp_wifi.h>
#include <ESPmDNS.h>
@ -65,11 +62,16 @@ conn_types_t ESPNetwork::preferredConnType() {
case conn_types_t::unset:
case conn_types_t::ap:
return conn_types_t::ap;
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
case conn_types_t::ethernetpref:
return settings.WIFI.ssid[0] != '\0' && (!ETH.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;
#else
// Stored by a backup or an older UI on a chip without Ethernet.
case conn_types_t::ethernetpref:
case conn_types_t::ethernet:
return settings.WIFI.ssid[0] != '\0' ? conn_types_t::wifi : conn_types_t::ap;
#endif
default:
return settings.connType;
@ -191,12 +193,12 @@ void ESPNetwork::emitSockets() {
}
}
void ESPNetwork::emitSockets(uint8_t num) {
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
if(this->connType == conn_types_t::ethernet) {
JsonSockEvent *json = sockEmit.beginEmit("ethernet");
json->beginObject();
json->addElem("connected", this->connected());
json->addElem("speed", ETH.linkSpeed());
json->addElem("speed", (uint32_t)ETH.linkSpeed());
json->addElem("fullduplex", ETH.fullDuplex());
json->endObject();
sockEmit.endEmit(num);
@ -254,7 +256,7 @@ void ESPNetwork::setConnected(conn_types_t connType) {
this->channel = WiFi.channel();
this->connectAttempts++;
}
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
else if(this->connType == conn_types_t::ethernet) {
if(this->softAPOpened) {
ESP_LOGI(TAG, "Disconnecting from SoftAP");
@ -265,7 +267,7 @@ void ESPNetwork::setConnected(conn_types_t connType) {
this->_connecting = false;
this->wifiFallback = false;
}
#endif // CONFIG_IDF_TARGET_ESP32C6
#endif // HAS_ETHERNET
// NET: Begin this in the startup.
//sockEmit.begin();
safe_wdt_reset();
@ -281,7 +283,7 @@ void ESPNetwork::setConnected(conn_types_t connType) {
settings.IP.dns2 = WiFi.dnsIP(1);
}
}
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
else {
ESP_LOGI(TAG, "Successfully Connected to Ethernet!!! %s%s %dMbps", ETH.localIP().toString().c_str(), ETH.fullDuplex() ? " FULL DUPLEX" : "", ETH.linkSpeed());
if(settings.IP.dhcp) {
@ -295,7 +297,7 @@ void ESPNetwork::setConnected(conn_types_t connType) {
JsonSockEvent *json = sockEmit.beginEmit("ethernet");
json->beginObject();
json->addElem("connected", this->connected());
json->addElem("speed", ETH.linkSpeed());
json->addElem("speed", (uint32_t)ETH.linkSpeed());
json->addElem("fullduplex", ETH.fullDuplex());
json->endObject();
sockEmit.endEmit();
@ -307,7 +309,7 @@ void ESPNetwork::setConnected(conn_types_t connType) {
if(this->connType == conn_types_t::wifi) {
ESP_LOGI(TAG, "Reconnected after %.3fsec IP: %s %s CH:%d (%d dBm) Disconnected %d times", 1.0 * (millis() - this->connectStart)/1000, WiFi.localIP().toString().c_str(), this->mac.c_str(), this->channel, this->strength, this->connectAttempts - 1);
}
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
else {
ESP_LOGI(TAG, "Reconnected after %.3fsec IP: %s%s %dMbps Disconnected %d times", 1.0 * (millis() - this->connectStart)/1000, ETH.localIP().toString().c_str(), ETH.fullDuplex() ? " FULL DUPLEX" : "", ETH.linkSpeed(), this->connectAttempts - 1);
}
@ -355,7 +357,7 @@ void ESPNetwork::setConnected(conn_types_t connType) {
settings.printAvailHeap();
this->needsBroadcast = true;
}
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
bool ESPNetwork::connectWired() {
if(ETH.linkUp()) {
// If the ethernet link is re-established then we need to shut down wifi.
@ -389,7 +391,13 @@ bool ESPNetwork::connectWired() {
else
ETH.setHostname("ESPSomfy-RTS");
ESP_LOGD(TAG, "Set hostname to: %s", ETH.getHostname());
if(!ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, settings.Ethernet.phyType, settings.Ethernet.CLKMode)) {
eth_phy_type_t phyType;
bool begun = false;
if(!ethPhyType(settings.Ethernet.phyType, phyType))
ESP_LOGE(TAG, "Ethernet PHY type %u is not supported", (unsigned int)settings.Ethernet.phyType);
else
begun = ETH.begin(settings.Ethernet.phyAddress, settings.Ethernet.PWRPin, settings.Ethernet.MDCPin, settings.Ethernet.MDIOPin, phyType, settings.Ethernet.CLKMode);
if(!begun) {
ESP_LOGE(TAG, "Ethernet Begin failed");
this->ethStarted = false;
if(settings.connType == conn_types_t::ethernetpref) {
@ -412,10 +420,10 @@ bool ESPNetwork::connectWired() {
this->connectStart = millis();
return true;
}
#endif // CONFIG_IDF_TARGET_ESP32C6
#endif // HAS_ETHERNET
void ESPNetwork::updateHostname() {
if(settings.hostname[0] != '\0' && this->connected()) {
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
if(this->connType == conn_types_t::ethernet &&
strcmp(settings.hostname, ETH.getHostname()) != 0) {
ESP_LOGD(TAG, "Updating host name to %s...", settings.hostname);
@ -511,7 +519,7 @@ bool ESPNetwork::connect(conn_types_t ctype) {
esp_task_wdt_reset();
if(this->connecting()) return true;
if(this->disconnectTime == 0) this->disconnectTime = millis();
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
if(ctype == conn_types_t::ethernet && this->connType != conn_types_t::ethernet) {
// Here we need to call the connect to ethernet.
this->connectWired();
@ -588,7 +596,7 @@ bool ESPNetwork::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;
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
else if(this->connType == conn_types_t::ethernet) return ETH.linkUp();
#endif
else return this->connType != conn_types_t::unset;
@ -635,7 +643,7 @@ void ESPNetwork::networkEvent(WiFiEvent_t event) {
net.setConnected(conn_types_t::wifi);
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP: ESP_LOGW(TAG, "Lost IP address and IP address is reset to 0"); break;
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
case ARDUINO_EVENT_ETH_GOT_IP:
// If the Wifi is connected then drop that connection
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true);
@ -680,7 +688,7 @@ void ESPNetwork::networkEvent(WiFiEvent_t event) {
net.softAPOpened = false;
break;
default:
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
if(event > ARDUINO_EVENT_ETH_START)
ESP_LOGW(TAG, "(evt) Unknown Ethernet Event %d", event);
#endif

45
src/EthPhy.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef ETH_PHY_H
#define ETH_PHY_H
#include <stdint.h>
#if __has_include(<sdkconfig.h>)
#include <sdkconfig.h>
#endif
// Ethernet needs the RMII MAC, which only the original ESP32 has. The C3, S3
// and C6 build without it.
#if defined(CONFIG_ETH_USE_ESP32_EMAC) && CONFIG_ETH_USE_ESP32_EMAC
#define HAS_ETHERNET 1
#else
#define HAS_ETHERNET 0
#endif
// PHY chip ids as stored in NVS and backups and sent to the web UI. They are
// Arduino 2.x's eth_phy_type_t values, frozen: Arduino 3.x renumbered that
// enum, so an id is translated by name when Ethernet starts, never cast.
enum class eth_phy_id_t : uint8_t {
LAN8720 = 0,
TLK110 = 1,
RTL8201 = 2,
DP83848 = 3,
DM9051 = 4, // An SPI chip. The RMII MAC can't drive it.
KSZ8041 = 5,
KSZ8081 = 6
};
#if HAS_ETHERNET
#include <ETH.h>
// Sets type to the framework's PHY for a stored id. False for an id the RMII
// MAC can't drive.
inline bool ethPhyType(uint8_t id, eth_phy_type_t &type) {
switch(static_cast<eth_phy_id_t>(id)) {
case eth_phy_id_t::LAN8720: type = ETH_PHY_LAN8720; return true;
case eth_phy_id_t::TLK110: type = ETH_PHY_TLK110; return true;
case eth_phy_id_t::RTL8201: type = ETH_PHY_RTL8201; return true;
case eth_phy_id_t::DP83848: type = ETH_PHY_DP83848; return true;
case eth_phy_id_t::KSZ8041: type = ETH_PHY_KSZ8041; return true;
case eth_phy_id_t::KSZ8081: type = ETH_PHY_KSZ8081; return true;
default: return false;
}
}
#endif
#endif

View file

@ -382,22 +382,11 @@ void SSDPClass::_parsePacket(ssdp_packet_t *pkt, AsyncUDPPacket &p) {
}
IPAddress SSDPClass::localIP()
{
// Make sure we don't get a null IPAddress.
#ifdef CONFIG_IDF_TARGET_ESP32C6
return WiFi.localIP();
#else
tcpip_adapter_ip_info_t ip;
if (WiFi.getMode() == WIFI_STA) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip)) {
return IPAddress();
}
} else if (WiFi.getMode() == WIFI_OFF) {
if (tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip)) {
return IPAddress();
}
}
return IPAddress(ip.ip.addr);
#if HAS_ETHERNET
// Wi-Fi is switched off while Ethernet is connected.
if (WiFi.getMode() == WIFI_OFF) return ETH.localIP();
#endif
return WiFi.localIP();
}
void SSDPClass::_sendResponse(IPAddress addr, uint16_t port, UPNPDeviceType *d, const char *st, response_types_t responseType) {
char buffer[1460];

View file

@ -50,7 +50,7 @@ int8_t SomfyShade::validateJSON(JsonObject &obj) {
pin_users_t radio, ethernet, others;
if(somfy.transceiver.config.enabled)
radio.uses = [](uint8_t pin, void *) { return somfy.transceiver.usesPin(pin); };
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
if(settings.connType == conn_types_t::ethernet || settings.connType == conn_types_t::ethernetpref)
ethernet.uses = [](uint8_t pin, void *) { return settings.Ethernet.usesPin(pin); };
#endif

View file

@ -802,7 +802,7 @@ void registerWebUiRoutes(AsyncWebServer &server) {
JsonObject objEth = obj["ethernet"];
if(settings.connType == conn_types_t::ethernet || settings.connType == conn_types_t::ethernetpref)
reboot = true;
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
settings.Ethernet.fromJSON(objEth);
settings.Ethernet.save();
#endif
@ -886,8 +886,10 @@ void registerWebUiRoutes(AsyncWebServer &server) {
JsonObject obj = doc.to<JsonObject>();
settings.toJSON(obj);
obj["fwVersion"] = settings.fwVersion.name;
// The web UI hides the Ethernet settings when the chip has no MAC.
obj["hasEthernet"] = HAS_ETHERNET == 1;
JsonObject eth = obj.createNestedObject("ethernet");
#ifndef CONFIG_IDF_TARGET_ESP32C6
#if HAS_ETHERNET
settings.Ethernet.toJSON(eth);
#endif
JsonObject wifi = obj.createNestedObject("wifi");