ESPHome 2026.4.0-dev
Loading...
Searching...
No Matches
ethernet_component_rp2040.cpp
Go to the documentation of this file.
2
3#if defined(USE_ETHERNET) && defined(USE_RP2040)
4
7#include "esphome/core/log.h"
8
10
11#include <SPI.h>
12#include <lwip/dns.h>
13#include <lwip/netif.h>
14
15namespace esphome::ethernet {
16
17static const char *const TAG = "ethernet";
18
20 // Configure SPI pins
21 SPI.setRX(this->miso_pin_);
22 SPI.setTX(this->mosi_pin_);
23 SPI.setSCK(this->clk_pin_);
24
25 // Toggle reset pin if configured
26 if (this->reset_pin_ >= 0) {
27 rp2040::RP2040GPIOPin reset_pin;
28 reset_pin.set_pin(this->reset_pin_);
29 reset_pin.set_flags(gpio::FLAG_OUTPUT);
30 reset_pin.setup();
31 reset_pin.digital_write(false);
32 delay(1); // NOLINT
33 reset_pin.digital_write(true);
34 // W5100S needs 150ms for PLL lock; W5500/ENC28J60 need ~10ms
35 delay(RESET_DELAY_MS); // NOLINT
36 }
37
38 // Create the SPI Ethernet device instance
39#if defined(USE_ETHERNET_W5500)
40 this->eth_ = new Wiznet5500lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT
41#elif defined(USE_ETHERNET_W5100)
42 this->eth_ = new Wiznet5100lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT
43#elif defined(USE_ETHERNET_ENC28J60)
44 this->eth_ = new ENC28J60lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT
45#endif
46
47 // Set hostname before begin() so the LWIP netif gets it
48 this->eth_->hostname(App.get_name().c_str());
49
50 // Configure static IP if set (must be done before begin())
51#ifdef USE_ETHERNET_MANUAL_IP
52 if (this->manual_ip_.has_value()) {
53 IPAddress ip(this->manual_ip_->static_ip);
54 IPAddress gateway(this->manual_ip_->gateway);
55 IPAddress subnet(this->manual_ip_->subnet);
56 IPAddress dns1(this->manual_ip_->dns1);
57 IPAddress dns2(this->manual_ip_->dns2);
58 this->eth_->config(ip, gateway, subnet, dns1, dns2);
59 }
60#endif
61
62 // Begin with fixed MAC or auto-generated
63 bool success;
64 if (this->fixed_mac_.has_value()) {
65 success = this->eth_->begin(this->fixed_mac_->data());
66 } else {
67 success = this->eth_->begin();
68 }
69
70 if (!success) {
71 ESP_LOGE(TAG, "Failed to initialize Ethernet");
72 delete this->eth_; // NOLINT(cppcoreguidelines-owning-memory)
73 this->eth_ = nullptr;
74 this->mark_failed();
75 return;
76 }
77
78 // Make this the default interface for routing
79 this->eth_->setDefault(true);
80
81 // The arduino-pico LwipIntfDev automatically handles packet processing
82 // via __addEthernetPacketHandler when no interrupt pin is used,
83 // or via GPIO interrupt when one is provided.
84
85 // Don't set started_ here — let the link polling in loop() set it
86 // when the link is actually up. Setting it prematurely causes
87 // a "Starting → Stopped → Starting" log sequence because the chip
88 // needs time after begin() before the PHY link is ready.
89}
90
92 // On RP2040, we need to poll connection state since there are no events.
94
95 // Throttle link/IP polling to avoid excessive SPI transactions.
96 // W5500/ENC28J60 read PHY register via SPI on every linkStatus() call.
97 // W5100 can't detect link state, so we skip the SPI read and assume link-up.
98 // connected() reads netif->ip_addr without LwIPLock, but this is a single
99 // 32-bit aligned read (atomic on ARM) — worst case is a one-iteration-stale
100 // value, which is benign for polling.
101 if (this->eth_ != nullptr && now - this->last_link_check_ >= LINK_CHECK_INTERVAL) {
102 this->last_link_check_ = now;
103#if defined(USE_ETHERNET_W5100)
104 // W5100 can't detect link (isLinkDetectable() returns false), so linkStatus()
105 // returns Unknown — assume link is up after successful begin()
106 bool link_up = true;
107#else
108 bool link_up = this->eth_->linkStatus() == LinkON;
109#endif
110 bool has_ip = this->eth_->connected();
111
112 if (!link_up) {
113 if (this->started_) {
114 this->started_ = false;
115 this->connected_ = false;
116 }
117 } else {
118 if (!this->started_) {
119 this->started_ = true;
120 }
121 bool was_connected = this->connected_;
122 this->connected_ = has_ip;
123 if (this->connected_ && !was_connected) {
124#ifdef USE_ETHERNET_IP_STATE_LISTENERS
126#endif
127 }
128 }
129 }
130
131 // State machine
132 switch (this->state_) {
134 if (this->started_) {
135 ESP_LOGI(TAG, "Starting connection");
137 this->start_connect_();
138 }
139 break;
141 if (!this->started_) {
142 ESP_LOGI(TAG, "Stopped connection");
144 } else if (this->connected_) {
145 // connection established
146 ESP_LOGI(TAG, "Connected");
148
149 this->dump_connect_params_();
150 this->status_clear_warning();
151#ifdef USE_ETHERNET_CONNECT_TRIGGER
153#endif
154 } else if (now - this->connect_begin_ > 15000) {
155 ESP_LOGW(TAG, "Connecting failed; reconnecting");
156 this->start_connect_();
157 }
158 break;
160 if (!this->started_) {
161 ESP_LOGI(TAG, "Stopped connection");
163#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
165#endif
166 } else if (!this->connected_) {
167 ESP_LOGW(TAG, "Connection lost; reconnecting");
169 this->start_connect_();
170#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
172#endif
173 } else {
174 this->finish_connect_();
175 }
176 break;
177 }
178}
179
181 const char *type_str = "Unknown";
182#if defined(USE_ETHERNET_W5500)
183 type_str = "W5500";
184#elif defined(USE_ETHERNET_W5100)
185 type_str = "W5100";
186#elif defined(USE_ETHERNET_ENC28J60)
187 type_str = "ENC28J60";
188#endif
189 ESP_LOGCONFIG(TAG,
190 "Ethernet:\n"
191 " Type: %s\n"
192 " Connected: %s\n"
193 " CLK Pin: %u\n"
194 " MISO Pin: %u\n"
195 " MOSI Pin: %u\n"
196 " CS Pin: %u\n"
197 " IRQ Pin: %d\n"
198 " Reset Pin: %d",
199 type_str, YESNO(this->is_connected()), this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_,
200 this->interrupt_pin_, this->reset_pin_);
201 this->dump_connect_params_();
202}
203
205 network::IPAddresses addresses;
206 if (this->eth_ != nullptr) {
207 LwIPLock lock;
208 addresses[0] = network::IPAddress(this->eth_->localIP());
209 }
210 return addresses;
211}
212
213network::IPAddress EthernetComponent::get_dns_address(uint8_t num) {
214 LwIPLock lock;
215 const ip_addr_t *dns_ip = dns_getserver(num);
216 return dns_ip;
217}
218
220 if (this->eth_ != nullptr) {
221 this->eth_->macAddress(mac);
222 } else {
223 memset(mac, 0, 6);
224 }
225}
226
227std::string EthernetComponent::get_eth_mac_address_pretty() {
228 char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
229 return std::string(this->get_eth_mac_address_pretty_into_buffer(buf));
230}
231
233 std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
234 uint8_t mac[6];
236 format_mac_addr_upper(mac, buf.data());
237 return buf.data();
238}
239
241 // W5100, W5500, and ENC28J60 are full-duplex on RP2040
242 return ETH_DUPLEX_FULL;
243}
244
246#ifdef USE_ETHERNET_ENC28J60
247 // ENC28J60 is 10Mbps only
248 return ETH_SPEED_10M;
249#else
250 // W5100 and W5500 are 100Mbps
251 return ETH_SPEED_100M;
252#endif
253}
254
256 ESP_LOGI(TAG, "Powering down ethernet");
257 if (this->eth_ != nullptr) {
258 this->eth_->end();
259 }
260 this->connected_ = false;
261 this->started_ = false;
262 return true;
263}
264
266 this->got_ipv4_address_ = false;
267 this->connect_begin_ = millis();
268 this->status_set_warning(LOG_STR("waiting for IP configuration"));
269
270 // Hostname is already set in setup() via LwipIntf::setHostname()
271
272#ifdef USE_ETHERNET_MANUAL_IP
273 if (this->manual_ip_.has_value()) {
274 // Static IP was already configured before begin() in setup()
275 // Set DNS servers
276 LwIPLock lock;
277 if (this->manual_ip_->dns1.is_set()) {
278 ip_addr_t d;
279 d = this->manual_ip_->dns1;
280 dns_setserver(0, &d);
281 }
282 if (this->manual_ip_->dns2.is_set()) {
283 ip_addr_t d;
284 d = this->manual_ip_->dns2;
285 dns_setserver(1, &d);
286 }
287 }
288#endif
289}
290
292 // No additional work needed on RP2040 for now
293 // IPv6 link-local could be added here in the future
294}
295
297 if (this->eth_ == nullptr) {
298 return;
299 }
300
301 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
302 char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
303 char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
304 char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
305 char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
306 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
307
308 // Copy all lwIP state under the lock to avoid races with IRQ callbacks
309 ip_addr_t ip_addr, netmask, gw, dns1_addr, dns2_addr;
310 {
311 LwIPLock lock;
312 auto *netif = this->eth_->getNetIf();
313 ip_addr = netif->ip_addr;
314 netmask = netif->netmask;
315 gw = netif->gw;
316 dns1_addr = *dns_getserver(0);
317 dns2_addr = *dns_getserver(1);
318 }
319 ESP_LOGCONFIG(TAG,
320 " IP Address: %s\n"
321 " Hostname: '%s'\n"
322 " Subnet: %s\n"
323 " Gateway: %s\n"
324 " DNS1: %s\n"
325 " DNS2: %s\n"
326 " MAC Address: %s",
327 network::IPAddress(&ip_addr).str_to(ip_buf), App.get_name().c_str(),
328 network::IPAddress(&netmask).str_to(subnet_buf), network::IPAddress(&gw).str_to(gateway_buf),
329 network::IPAddress(&dns1_addr).str_to(dns1_buf), network::IPAddress(&dns2_addr).str_to(dns2_buf),
330 this->get_eth_mac_address_pretty_into_buffer(mac_buf));
331}
332
333void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
334void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; }
335void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; }
336void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; }
337void EthernetComponent::set_interrupt_pin(int8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
338void EthernetComponent::set_reset_pin(int8_t reset_pin) { this->reset_pin_ = reset_pin; }
339
340} // namespace esphome::ethernet
341
342#endif // USE_ETHERNET && USE_RP2040
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void mark_failed()
Mark this component as failed.
void status_clear_warning()
Definition component.h:293
constexpr const char * c_str() const
Definition string_ref.h:73
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:327
static constexpr uint32_t LINK_CHECK_INTERVAL
network::IPAddress get_dns_address(uint8_t num)
static constexpr uint32_t RESET_DELAY_MS
optional< std::array< uint8_t, 6 > > fixed_mac_
ESPDEPRECATED("Use get_eth_mac_address_pretty_into_buffer() instead. Removed in 2026.9.0", "2026.3.0") std const char * get_eth_mac_address_pretty_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
uint32_t ip_addr
in_addr ip_addr_t
Definition ip_address.h:22
@ FLAG_OUTPUT
Definition gpio.h:28
std::array< IPAddress, 5 > IPAddresses
Definition ip_address.h:187
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
Application App
Global storage of Application pointer - only one Application can exist.
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
Definition helpers.h:1420
static void uint32_t