Ethernet

The espp::Ethernet component wraps the ESP-IDF esp_eth APIs and brings up an Ethernet interface over either RMII (internal EMAC, on SoCs with SOC_EMAC_SUPPORTED) or SPI (an external MAC+PHY chip such as the WIZnet W5500), plus a pre-built-driver escape hatch for any other chip.

One class owns the boilerplate that BSPs otherwise duplicate - netif, event loop, netif glue, DHCP client/server, static IP, hostname, MAC assignment (explicit or eFuse-derived) and link/IP event dispatch to std::function callbacks - and, unlike the inline BSP implementations it replaces, provides a symmetric teardown (deinitialize() + destructor).

The interface-specific configuration is a single tagged Config::interface member (a std::variant of RmiiConfig / SpiConfig / DriverConfig); the rest of Config (DHCP mode, static IP, hostname, MAC, callbacks) is shared.

Note

The RMII path uses the generic 802.3 PHY driver in esp_eth core (no extra dependency). The concrete SPI chip drivers (W5500, DM9051, ENC28J60) are managed components pulled in only when the matching CONFIG_ESPP_ETHERNET_* Kconfig option is enabled.

API Reference

Header File

Classes

class Ethernet : public espp::BaseComponent

Cross-interface Ethernet wrapper around the ESP-IDF esp_eth APIs.

One class drives both RMII (internal EMAC, on SoCs with SOC_EMAC_SUPPORTED - esp32 / esp32-p4) and SPI (external MAC+PHY chips such as the WIZnet W5500) interfaces, plus a pre-built-driver escape hatch for any other chip. It owns all of the common boilerplate - netif, event loop, glue, attach, DHCP client/server, static IP, hostname, MAC assignment, link/IP event dispatch and, unlike the inline BSP implementations it replaces, a symmetric teardown (deinitialize + destructor).

The interface-specific part is a single tagged Config member: RmiiConfig, SpiConfig, or DriverConfig.

RMII Example

  // RMII on the ESP32-Ethernet-Kit (IP101 PHY on GPIO0 ref-clock). Adjust for your board.
  espp::Ethernet eth(with_callbacks({
      .interface = espp::Ethernet::RmiiConfig{.mdc_gpio = 23,
                                              .mdio_gpio = 18,
                                              .phy_addr = 1,
                                              .phy_reset_gpio = 5,
                                              .clock_ext_in = true,
                                              .clock_gpio = 0},
  }));
  eth.initialize();

SPI (W5500) Example

  // Bring up the SPI bus the W5500 lives on (adjust pins for your board).
  spi_bus_config_t buscfg = {};
  buscfg.miso_io_num = 13;
  buscfg.mosi_io_num = 11;
  buscfg.sclk_io_num = 12;
  buscfg.quadwp_io_num = -1;
  buscfg.quadhd_io_num = -1;
  buscfg.max_transfer_sz = 1600;
  ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));

  // One reactor-owned W5500 over that bus, DHCP client.
  espp::Ethernet eth(with_callbacks({
      .interface = espp::Ethernet::SpiConfig{.host = SPI2_HOST,
                                             .cs_gpio = 10,
                                             .int_gpio = 14,
                                             .reset_gpio = 21,
                                             .chip = espp::Ethernet::SpiChip::W5500},
  }));
  eth.initialize();

Note

The concrete SPI chip drivers (W5500, DM9051, ENC28J60) are ESP-IDF managed components pulled in only when the matching CONFIG_ESPP_ETHERNET_* option is enabled. The RMII path uses the generic 802.3 PHY driver in esp_eth core (no extra dependency), which supports common PHYs (IP101, LAN87xx, DP83848, RTL8201, KSZ8041).

Public Types

enum class DhcpMode

DHCP operating mode.

Values:

enumerator CLIENT

Acquire an IP from an upstream DHCP server (or use a static IP if ip_info is set).

enumerator SERVER

Run a DHCP server on this interface and assign IPs to connected hosts.

enum class PhyModel

PHY model hint for the RMII path. All are handled by the generic 802.3 driver in v1; the field is advisory (kept for a future specific-driver option) and does not change behavior today.

Values:

enumerator GENERIC
enumerator IP101
enumerator LAN87XX
enumerator DP83848
enumerator RTL8201
enumerator KSZ80XX
enum class SpiChip

Supported built-in SPI ethernet chips.

Values:

enumerator W5500
enumerator DM9051
enumerator ENC28J60
using LinkCallback = std::function<void()>

Callback for link up/down and IP-lost. Runs in the esp event-loop task context - keep it short and non-blocking.

using IpCallback = std::function<void(esp_ip4_addr_t ip)>

Callback for IP acquisition. Runs in the esp event-loop task context.

using ClientIpCallback = std::function<void(esp_ip4_addr_t ip, MacAddress mac)>

Callback (SERVER mode) invoked for each DHCP lease the server assigns.

Public Functions

explicit Ethernet(const Config &config)

Construct (does not start the interface; call initialize()).

Parameters:

config – The configuration.

~Ethernet()

Destroy - deinitializes the interface if still running.

bool initialize(std::error_code &ec)

Bring up the interface (create driver, attach netif, register events, apply IP/hostname/MAC, start). Idempotent.

Parameters:

ec – Set to a specific error on failure; cleared on success.

Returns:

true on success (interface started).

bool initialize()

Convenience overload that logs on failure.

Returns:

true on success.

bool deinitialize(std::error_code &ec)

Stop and fully tear down the interface (reverse-order unwind). Idempotent. Does not undo the process-global netif/event-loop init.

Parameters:

ec – Set on failure; cleared on success.

Returns:

true on success.

void deinitialize()

Convenience overload that logs on failure.

inline bool is_initialized() const
Returns:

true if the interface has been initialized/started.

inline bool is_connected() const
Returns:

true if the link is up AND an IP address is held.

Returns:

true if the physical link is up.

esp_ip4_addr_t ip() const
Returns:

The current IPv4 address (0 if none).

std::string get_ip_address() const
Returns:

The current IPv4 address as a dotted quad (“0.0.0.0” if none).

std::string get_mac_address() const
Returns:

The interface MAC as “aa:bb:cc:dd:ee:ff” (empty if not initialized).

Returns:

{speed_mbps, full_duplex} if the link is up, else nullopt.

inline esp_eth_handle_t native_handle() const
Returns:

The raw esp_eth driver handle (nullptr if not initialized).

inline esp_netif_t *netif() const
Returns:

The raw esp_netif handle (nullptr if not initialized).

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

struct Config

Full configuration.

Public Members

std::variant<RmiiConfig, SpiConfig, DriverConfig> interface

The interface: RMII (internal EMAC), SPI (external chip), or a pre-built driver.

DhcpMode mode = {DhcpMode::CLIENT}

DHCP client or server.

std::optional<MacAddress> mac_address = {}

Explicit MAC; else eFuse (ESP_MAC_ETH) for chips needing one.

std::string hostname = {}

netif hostname (empty = ESP-IDF default).

esp_netif_ip_info_t ip_info = {}

CLIENT: if ip.addr != 0, use this as a static IP (DHCP client is stopped); else DHCP. SERVER: the interface/gateway IP for the DHCP server (0 -> 192.168.4.1/24).

Physical link came up.

Physical link went down.

IpCallback on_got_ip = {}

Interface obtained an IPv4 address.

LinkCallback on_lost_ip = {}

Interface lost its IPv4 address.

ClientIpCallback on_client_assigned = {}

SERVER mode: a client was assigned an IP.

struct DriverConfig

Pre-built-driver escape hatch: the caller creates the MAC+PHY for any chip and hands them over. The component takes ownership (they are freed by esp_eth_driver_uninstall on teardown).

Public Members

esp_eth_mac_t *mac = {nullptr}

Caller-created MAC (ownership transfers on initialize()).

esp_eth_phy_t *phy = {nullptr}

Caller-created PHY (ownership transfers on initialize()).

bool needs_isr_service{false}

true if the driver uses a GPIO INT line (installs the ISR service).

bool needs_mac_assignment{false}

true if the chip has no factory MAC (assign from config/eFuse).

struct RmiiConfig

RMII / internal-EMAC interface configuration (esp32 / esp32-p4 only).

Public Members

int mdc_gpio = {-1}

SMI management clock (MDC) GPIO (required).

int mdio_gpio = {-1}

SMI management data (MDIO) GPIO (required).

int phy_addr = {-1}

PHY SMI address; -1 auto-detects.

int phy_reset_gpio = {-1}

Active-low PHY reset GPIO; -1 = none.

PhyModel phy = {PhyModel::GENERIC}

PHY model hint (advisory; see PhyModel).

bool clock_ext_in = {true}

true: external RMII 50 MHz ref-clock in; false: internal out.

int clock_gpio = {0}

RMII REF_CLK GPIO (fixed set on esp32; routable on esp32-p4).

struct DataPins

RMII data-plane pins. Only used on SoCs with routable EMAC pins (esp32-p4); ignored on esp32 where they are fixed via IO_MUX.

struct SpiConfig

SPI interface configuration (external MAC+PHY chip on a caller-owned, already-initialized SPI bus). Works on any SoC.

Public Members

spi_host_device_t host{SPI2_HOST}

SPI host whose bus the caller already initialized (required).

int cs_gpio = {-1}

Chip-select GPIO (required).

int int_gpio = {-1}

Interrupt GPIO; -1 to poll instead.

int reset_gpio = {-1}

Chip reset GPIO; -1 = none.

int clock_speed_hz = {20000000}

SPI clock (W5500 max ~33 MHz; conservative default).

int phy_addr = {1}

PHY address (W5500 = 1).

SpiChip chip = {SpiChip::W5500}

Which built-in SPI ethernet chip.