ESPHome 2026.9.0-dev
Loading...
Searching...
No Matches
ethernet_component_esp32.cpp
Go to the documentation of this file.
2
3#if defined(USE_ETHERNET) && defined(USE_ESP32)
4
7#include "esphome/core/log.h"
8#include "w5500_custom_spi.h"
9
10#include <lwip/dns.h>
11#include <cinttypes>
12#include "esp_event.h"
13
14// IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry;
15// they are no longer included via esp_eth.h and need explicit includes.
16// On IDF 5.x these headers don't exist as standalone files.
17#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
18#ifdef USE_ETHERNET_LAN8720
19#include "esp_eth_phy_lan87xx.h"
20#endif
21#ifdef USE_ETHERNET_RTL8201
22#include "esp_eth_phy_rtl8201.h"
23#endif
24#ifdef USE_ETHERNET_DP83848
25#include "esp_eth_phy_dp83848.h"
26#endif
27#ifdef USE_ETHERNET_IP101
28#include "esp_eth_phy_ip101.h"
29#endif
30#ifdef USE_ETHERNET_KSZ8081
31#include "esp_eth_phy_ksz80xx.h"
32#endif
33#ifdef USE_ETHERNET_W5500
34#include "esp_eth_mac_w5500.h"
35#include "esp_eth_phy_w5500.h"
36#endif
37#ifdef USE_ETHERNET_DM9051
38#include "esp_eth_mac_dm9051.h"
39#include "esp_eth_phy_dm9051.h"
40#endif
41#endif // ESP_IDF_VERSION >= 6.0.0
42
43// LAN867x header exists on all IDF versions (external component since IDF 5.3)
44#ifdef USE_ETHERNET_LAN8670
45#include "esp_eth_phy_lan867x.h"
46#endif
47
48// ENC28J60 header exists on all IDF versions (always an external component)
49#ifdef USE_ETHERNET_ENC28J60
50#include "esp_eth_enc28j60.h"
51#endif
52
53// CH390 headers exist on all IDF versions (always an external component)
54#ifdef USE_ETHERNET_CH390
55#include "esp_eth_mac_ch390.h"
56#include "esp_eth_phy_ch390.h"
57#endif
58
59#ifdef USE_ETHERNET_SPI
60#include <driver/gpio.h>
61#include <driver/spi_master.h>
62#ifdef USE_SPI
64#endif
65#endif
66
67namespace esphome::ethernet {
68
69static const char *const TAG = "ethernet";
70
71// PHY register size for hex logging
72static constexpr size_t PHY_REG_SIZE = 2;
73
75 ESP_LOGE(TAG, "%s: (%d) %s", message, err, esp_err_to_name(err));
76 this->mark_failed();
77}
78
79#define ESPHL_ERROR_CHECK(err, message) \
80 if ((err) != ESP_OK) { \
81 this->log_error_and_mark_failed_(err, message); \
82 return; \
83 }
84
85#define ESPHL_ERROR_CHECK_RET(err, message, ret) \
86 if ((err) != ESP_OK) { \
87 this->log_error_and_mark_failed_(err, message); \
88 return ret; \
89 }
90
93
94 switch (this->state_) {
96 if (this->started_) {
97 ESP_LOGI(TAG, "Starting connection");
99 this->start_connect_();
100 }
101 break;
103 if (!this->started_) {
104 ESP_LOGI(TAG, "Stopped connection");
106 } else if (this->connected_) {
107 // connection established
108 ESP_LOGI(TAG, "Connected");
110
111 this->dump_connect_params_();
112 this->status_clear_warning();
113#ifdef USE_ETHERNET_CONNECT_TRIGGER
115#endif
116 } else if (now - this->connect_begin_ > 15000) {
117 ESP_LOGW(TAG, "Connecting failed; reconnecting");
118 this->start_connect_();
119 }
120 break;
122 if (!this->started_) {
123 ESP_LOGI(TAG, "Stopped connection");
125#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
127#endif
128 } else if (!this->connected_) {
129 ESP_LOGW(TAG, "Connection lost; reconnecting");
131 this->start_connect_();
132#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
134#endif
135 } else {
136 this->finish_connect_();
137 // When connected and stable, disable the loop to save CPU cycles
138 this->disable_loop();
139 }
140 break;
141 }
142}
143
145 if (esp_reset_reason() != ESP_RST_DEEPSLEEP) {
146 // Delay here to allow power to stabilise before Ethernet is initialized.
147 delay(300); // NOLINT
148 }
149
150 if (this->enable_on_boot_) {
151 this->ethernet_lazy_init_();
152 if (!this->ethernet_initialized_) {
153 // lazy_init bailed early via ESPHL_ERROR_CHECK or mark_failed; nothing more to do.
154 return;
155 }
156 esp_err_t err = esp_eth_start(this->eth_handle_);
157 ESPHL_ERROR_CHECK(err, "ETH start error");
158 } else {
159 ESP_LOGCONFIG(TAG, "Skipping init (enable_on_boot: false)");
160 this->disabled_ = true;
161 }
162}
163
165 if (this->ethernet_initialized_)
166 return;
167
168 esp_err_t err;
169
170#ifdef USE_ETHERNET_SPI
171 // Install GPIO ISR handler to be able to service SPI Eth modules interrupts
172 gpio_install_isr_service(0);
173
174 spi_host_device_t host;
175#ifdef USE_SPI
176 if (this->spi_parent_ != nullptr) {
177 // The bus is owned and already initialized by the spi component; share its host.
178 host = this->spi_parent_->get_interface();
179 } else
180#endif
181 {
182 spi_bus_config_t buscfg = {
183 .mosi_io_num = this->mosi_pin_,
184 .miso_io_num = this->miso_pin_,
185 .sclk_io_num = this->clk_pin_,
186 .quadwp_io_num = -1,
187 .quadhd_io_num = -1,
188 .data4_io_num = -1,
189 .data5_io_num = -1,
190 .data6_io_num = -1,
191 .data7_io_num = -1,
192 .max_transfer_sz = 0,
193 .flags = 0,
194 .intr_flags = 0,
195 };
196
197 host = this->interface_;
198
199 err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO);
200 ESPHL_ERROR_CHECK(err, "SPI bus initialize error");
201 }
202#endif
203 // Network interface setup handled by network component
204
205 esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH();
206 this->eth_netif_ = esp_netif_new(&cfg);
207
208 // Init MAC and PHY configs to default
209 eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
210 eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
211
212#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module
213 spi_device_interface_config_t devcfg = {
214 .command_bits = 0,
215 .address_bits = 0,
216 .dummy_bits = 0,
217 .mode = 0,
218 .duty_cycle_pos = 0,
219 .cs_ena_pretrans = 0,
220 .cs_ena_posttrans = 0,
221 .clock_speed_hz = this->clock_speed_,
222 .input_delay_ns = 0,
223 .spics_io_num = this->cs_pin_,
224 .flags = 0,
225 .queue_size = 20,
226 .pre_cb = nullptr,
227 .post_cb = nullptr,
228 };
229
230#if defined(USE_ETHERNET_W5500)
231 eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
232#elif defined(USE_ETHERNET_DM9051)
233 eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg);
234#elif defined(USE_ETHERNET_ENC28J60)
235 eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG(host, &devcfg);
236#elif defined(USE_ETHERNET_CH390)
237 eth_ch390_config_t ch390_config = ETH_CH390_DEFAULT_CONFIG(host, &devcfg);
238#endif
239
240#if defined(USE_ETHERNET_W5500)
241 w5500_config.int_gpio_num = this->interrupt_pin_;
242#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
243 w5500_config.poll_period_ms = this->polling_interval_;
244#endif
245 // Install the custom SPI driver that offloads the bulk RX/TX frame transfers off the busy-wait
246 // path. w5500_config (and the devcfg it references) outlives esp_eth_mac_new_w5500() below, which
247 // runs the driver's init().
248 install_w5500_async_spi(w5500_config);
249#elif defined(USE_ETHERNET_DM9051)
250 dm9051_config.int_gpio_num = this->interrupt_pin_;
251#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
252 dm9051_config.poll_period_ms = this->polling_interval_;
253#endif
254#elif defined(USE_ETHERNET_ENC28J60)
255 // ENC28J60 does not support poll_period_ms. CS must stay asserted for the chip's CS hold
256 // time (t10, 210 ns) after the last clock or MAC/MII register reads fail ("wrong chip ID")
257 enc28j60_config.spi_devcfg->cs_ena_posttrans = enc28j60_cal_spi_cs_hold_time((this->clock_speed_ + 999999) / 1000000);
258 enc28j60_config.int_gpio_num = this->interrupt_pin_;
259#elif defined(USE_ETHERNET_CH390)
260 ch390_config.int_gpio_num = this->interrupt_pin_;
261#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
262 ch390_config.poll_period_ms = this->polling_interval_;
263#endif
264#endif
265
266 phy_config.phy_addr = this->phy_addr_spi_;
267 phy_config.reset_gpio_num = this->reset_pin_;
268
269 esp_eth_mac_t *mac = nullptr;
270#elif defined(USE_ETHERNET_OPENETH)
271 esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config);
272#else
273 phy_config.phy_addr = this->phy_addr_;
274 phy_config.reset_gpio_num = this->power_pin_;
275
276 eth_esp32_emac_config_t esp32_emac_config = eth_esp32_emac_default_config();
277#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
278 esp32_emac_config.smi_gpio.mdc_num = this->mdc_pin_;
279 esp32_emac_config.smi_gpio.mdio_num = this->mdio_pin_;
280#else
281 esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
282 esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_;
283#endif
284 // The RGMII types (GENERIC, YT8531) use the RGMII interface and default GPIO map from
285 // eth_esp32_emac_default_config(); writing the RMII clock config would clobber that
286 // union, so skip the RMII clock override for them.
287 if (this->type_ != ETHERNET_TYPE_GENERIC && this->type_ != ETHERNET_TYPE_YT8531) {
288 esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_;
289 esp32_emac_config.clock_config.rmii.clock_gpio =
290 static_cast<decltype(esp32_emac_config.clock_config.rmii.clock_gpio)>(this->clk_pin_);
291 }
292
293 esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
294#endif
295
296 switch (this->type_) {
297#ifdef USE_ETHERNET_OPENETH
299 phy_config.autonego_timeout_ms = 1000;
300#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
301 this->phy_ = esp_eth_phy_new_generic(&phy_config);
302#else
303 this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
304#endif
305 break;
306 }
307#endif
308#if CONFIG_ETH_USE_ESP32_EMAC
309#ifdef USE_ETHERNET_LAN8720
311 this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
312 break;
313 }
314#endif
315#ifdef USE_ETHERNET_RTL8201
317 this->phy_ = esp_eth_phy_new_rtl8201(&phy_config);
318 break;
319 }
320#endif
321#ifdef USE_ETHERNET_DP83848
323 this->phy_ = esp_eth_phy_new_dp83848(&phy_config);
324 break;
325 }
326#endif
327#ifdef USE_ETHERNET_IP101
328 case ETHERNET_TYPE_IP101: {
329 this->phy_ = esp_eth_phy_new_ip101(&phy_config);
330 break;
331 }
332#endif
333#ifdef USE_ETHERNET_JL1101
335 // PlatformIO (pioarduino): builtin esp_eth_phy_new_jl1101() on all IDF versions
336 // Non-PlatformIO: custom ESPHome driver (esp_eth_phy_jl1101.c)
337 this->phy_ = esp_eth_phy_new_jl1101(&phy_config);
338 break;
339 }
340#endif
341#ifdef USE_ETHERNET_KSZ8081
344 this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
345 break;
346 }
347#endif
348#ifdef USE_ETHERNET_LAN8670
350 this->phy_ = esp_eth_phy_new_lan867x(&phy_config);
351 break;
352 }
353#endif
354#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
355 // GENERIC and YT8531 both use the built-in generic 802.3 PHY driver; YT8531 gets
356 // extra chip-specific tuning applied later in ethernet_lazy_init_().
357#ifdef USE_ETHERNET_GENERIC
359#endif
360#ifdef USE_ETHERNET_YT8531
362#endif
363#if defined(USE_ETHERNET_GENERIC) || defined(USE_ETHERNET_YT8531)
364 this->phy_ = esp_eth_phy_new_generic(&phy_config);
365 break;
366#endif
367#endif
368#endif
369#ifdef USE_ETHERNET_SPI
370#if defined(USE_ETHERNET_W5500)
371 case ETHERNET_TYPE_W5500: {
372 mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
373 this->phy_ = esp_eth_phy_new_w5500(&phy_config);
374 break;
375 }
376#elif defined(USE_ETHERNET_DM9051)
378 mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
379 this->phy_ = esp_eth_phy_new_dm9051(&phy_config);
380 break;
381 }
382#elif defined(USE_ETHERNET_ENC28J60)
384 mac = esp_eth_mac_new_enc28j60(&enc28j60_config, &mac_config);
385 this->phy_ = esp_eth_phy_new_enc28j60(&phy_config);
386 break;
387 }
388#elif defined(USE_ETHERNET_CH390)
389 case ETHERNET_TYPE_CH390: {
390 mac = esp_eth_mac_new_ch390(&ch390_config, &mac_config);
391 this->phy_ = esp_eth_phy_new_ch390(&phy_config);
392 break;
393 }
394#endif
395#endif
396 default: {
397 this->mark_failed();
398 return;
399 }
400 }
401
402 esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_);
403 this->eth_handle_ = nullptr;
404 err = esp_eth_driver_install(&eth_config, &this->eth_handle_);
405 ESPHL_ERROR_CHECK(err, "ETH driver install error");
406
407#ifndef USE_ETHERNET_SPI
408#ifdef USE_ETHERNET_KSZ8081
409 if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) {
410 // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
412 }
413#endif // USE_ETHERNET_KSZ8081
414
415 for (const auto &phy_register : this->phy_registers_) {
416 this->write_phy_register_(mac, phy_register);
417 }
418
419#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
420#ifdef USE_ETHERNET_GENERIC
421 // The generic 802.3 PHY driver only resets the PHY in its init; it never enables
422 // auto-negotiation. A PHY that resets into a forced-speed mode (BMCR auto-nego bit
423 // clear) therefore stays there, and esp_eth_start() skips negotiation because the
424 // driver cached auto_nego_en=false at install time. Force auto-negotiation on here
425 // (which also updates that cached state) so esp_eth_start() restarts a proper
426 // negotiation. (YT8531 does this as part of its own chip-specific init below.)
427 if (this->type_ == ETHERNET_TYPE_GENERIC) {
428 bool autoneg_enable = true;
429 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_AUTONEGO, &autoneg_enable);
430 ESPHL_ERROR_CHECK(err, "Enable auto-negotiation failed");
431 }
432#endif
433#ifdef USE_ETHERNET_YT8531
434 if (this->type_ == ETHERNET_TYPE_YT8531) {
435 this->yt8531_phy_init_();
436 if (this->is_failed())
437 return;
438 }
439#endif
440#endif // ESP_IDF_VERSION >= 6.0.0
441#endif // !USE_ETHERNET_SPI
442
443 // use ESP internal eth mac
444 uint8_t mac_addr[MAC_ADDRESS_SIZE];
445 if (this->fixed_mac_.has_value()) {
446 memcpy(mac_addr, this->fixed_mac_->data(), MAC_ADDRESS_SIZE);
447 } else {
448 esp_read_mac(mac_addr, ESP_MAC_ETH);
449 }
450 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr);
451 ESPHL_ERROR_CHECK(err, "set mac address error");
452
453 /* attach Ethernet driver to TCP/IP stack */
454 err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_));
455 ESPHL_ERROR_CHECK(err, "ETH netif attach error");
456
457 // Register user defined event handers
458 err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &EthernetComponent::eth_event_handler, nullptr);
459 ESPHL_ERROR_CHECK(err, "ETH event handler register error");
460 err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr);
461 ESPHL_ERROR_CHECK(err, "GOT IP event handler register error");
462#if USE_NETWORK_IPV6
463 err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr);
464 ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error");
465#endif /* USE_NETWORK_IPV6 */
466
467 this->ethernet_initialized_ = true;
468}
469
471 if (!this->disabled_)
472 return;
473
474 ESP_LOGD(TAG, "Enabling");
475 this->ethernet_lazy_init_();
476 if (!this->ethernet_initialized_) {
477 ESP_LOGE(TAG, "Cannot enable - init failed");
478 return;
479 }
480 esp_err_t err = esp_eth_start(this->eth_handle_);
481 if (err != ESP_OK) {
482 ESP_LOGE(TAG, "esp_eth_start failed: %s", esp_err_to_name(err));
483 return;
484 }
485 this->disabled_ = false;
486 // The ETH_EVENT_START handler will set started_=true; the loop state machine
487 // will then drive the STOPPED -> CONNECTING -> CONNECTED transitions.
488 this->enable_loop();
489}
490
492 if (this->disabled_)
493 return;
494
495 ESP_LOGD(TAG, "Disabling");
496 esp_err_t err = esp_eth_stop(this->eth_handle_);
497 if (err != ESP_OK) {
498 ESP_LOGW(TAG, "esp_eth_stop failed: %s — disabling anyway", esp_err_to_name(err));
499 }
500 this->disabled_ = true;
501 // ETH_EVENT_STOP will clear started_; loop() will transition to STOPPED.
502}
503
505 const char *eth_type;
506 switch (this->type_) {
507#ifdef USE_ETHERNET_LAN8720
509 eth_type = "LAN8720";
510 break;
511#endif
512#ifdef USE_ETHERNET_RTL8201
514 eth_type = "RTL8201";
515 break;
516#endif
517#ifdef USE_ETHERNET_DP83848
519 eth_type = "DP83848";
520 break;
521#endif
522#ifdef USE_ETHERNET_IP101
524 eth_type = "IP101";
525 break;
526#endif
527#ifdef USE_ETHERNET_JL1101
529 eth_type = "JL1101";
530 break;
531#endif
532#ifdef USE_ETHERNET_KSZ8081
534 eth_type = "KSZ8081";
535 break;
536
538 eth_type = "KSZ8081RNA";
539 break;
540#endif
541#if defined(USE_ETHERNET_W5500)
543 eth_type = "W5500";
544 break;
545#elif defined(USE_ETHERNET_DM9051)
547 eth_type = "DM9051";
548 break;
549#elif defined(USE_ETHERNET_ENC28J60)
551 eth_type = "ENC28J60";
552 break;
553#elif defined(USE_ETHERNET_CH390)
555 eth_type = "CH390";
556 break;
557#endif
558#ifdef USE_ETHERNET_OPENETH
560 eth_type = "OPENETH";
561 break;
562#endif
563#ifdef USE_ETHERNET_LAN8670
565 eth_type = "LAN8670";
566 break;
567#endif
568#ifdef USE_ETHERNET_GENERIC
570 eth_type = "Generic (RGMII)";
571 break;
572#endif
573#ifdef USE_ETHERNET_YT8531
575 eth_type = "YT8531 (RGMII)";
576 break;
577#endif
578
579 default:
580 eth_type = "Unknown";
581 break;
582 }
583
584 ESP_LOGCONFIG(TAG,
585 "Ethernet:\n"
586 " Connected: %s",
587 YESNO(this->is_connected()));
588 this->dump_connect_params_();
589#ifdef USE_ETHERNET_SPI
590#ifdef USE_SPI
591 if (this->spi_parent_ != nullptr) {
592 // Pins and interface come from the shared spi bus; only CS is ours.
593 ESP_LOGCONFIG(TAG, " CS Pin: %u", this->cs_pin_);
594 } else
595#endif
596 {
597 ESP_LOGCONFIG(TAG,
598 " CLK Pin: %u\n"
599 " MISO Pin: %u\n"
600 " MOSI Pin: %u\n"
601 " CS Pin: %u",
602 this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_);
603 const char *spi_interface = "spi3";
604 if (this->interface_ == SPI2_HOST) {
605 spi_interface = "spi2";
606 }
607 ESP_LOGCONFIG(TAG, " Interface: %s", spi_interface);
608 }
609#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
610 if (this->polling_interval_ != 0) {
611 ESP_LOGCONFIG(TAG, " Polling Interval: %" PRIu32 " ms", this->polling_interval_);
612 } else
613#endif
614 {
615 ESP_LOGCONFIG(TAG, " IRQ Pin: %d", this->interrupt_pin_);
616 }
617 ESP_LOGCONFIG(TAG,
618 " Reset Pin: %d\n"
619 " Clock Speed: %d MHz",
620 this->reset_pin_, this->clock_speed_ / 1000000);
621#else
622 if (this->power_pin_ != -1) {
623 ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_);
624 }
625 ESP_LOGCONFIG(TAG,
626 " CLK Pin: %u\n"
627 " MDC Pin: %u\n"
628 " MDIO Pin: %u\n"
629 " PHY addr: %u",
630 this->clk_pin_, this->mdc_pin_, this->mdio_pin_, this->phy_addr_);
631#endif
632 ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
633}
634
636 network::IPAddresses addresses;
637 if (!this->ethernet_initialized_)
638 return addresses; // all-zero IPs
639 esp_netif_ip_info_t ip;
640 esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip);
641 if (err != ESP_OK) {
642 ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err));
643 // TODO: do something smarter
644 // return false;
645 } else {
646 addresses[0] = network::IPAddress(&ip.ip);
647 }
648#if USE_NETWORK_IPV6
649 struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
650 uint8_t count = 0;
651 count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
652 assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
653 assert(count < addresses.size());
654 for (int i = 0; i < count; i++) {
655 addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
656 }
657#endif /* USE_NETWORK_IPV6 */
658
659 return addresses;
660}
661
664 const ip_addr_t *dns_ip = dns_getserver(num);
665 return dns_ip;
666}
667
668void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) {
669 const char *event_name;
670
671 switch (event) {
672 case ETHERNET_EVENT_START:
673 event_name = "ETH started";
676 break;
677 case ETHERNET_EVENT_STOP:
678 event_name = "ETH stopped";
681 global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
682 break;
683 case ETHERNET_EVENT_CONNECTED:
684 event_name = "ETH connected";
685 // For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
686#if defined(USE_ETHERNET_IP_STATE_LISTENERS) && defined(USE_ETHERNET_MANUAL_IP)
687 if (global_eth_component->manual_ip_.has_value()) {
689 }
690#endif
691 break;
692 case ETHERNET_EVENT_DISCONNECTED:
693 event_name = "ETH disconnected";
695 global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
696 break;
697 default:
698 return;
699 }
700
701 ESP_LOGV(TAG, "[Ethernet event] %s (num=%" PRId32 ")", event_name, event);
702}
703
704void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
705 void *event_data) {
706 ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
707 const esp_netif_ip_info_t *ip_info = &event->ip_info;
708 ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip));
710#if USE_NETWORK_IPV6 && (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
711 global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT;
712 global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
713#else
715 global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
716#endif /* USE_NETWORK_IPV6 */
717#ifdef USE_ETHERNET_IP_STATE_LISTENERS
719#endif
720}
721
722#if USE_NETWORK_IPV6
723void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
724 void *event_data) {
725 ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
726 ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
728#if (USE_NETWORK_MIN_IPV6_ADDR_COUNT > 0)
730 global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
731 global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
732#else
734 global_eth_component->enable_loop_soon_any_context(); // Enable loop when connection state changes
735#endif
736#ifdef USE_ETHERNET_IP_STATE_LISTENERS
738#endif
739}
740#endif /* USE_NETWORK_IPV6 */
741
743#if USE_NETWORK_IPV6
744 // Retry IPv6 link-local setup if it failed during initial connect
745 // This handles the case where min_ipv6_addr_count is NOT set (or is 0),
746 // allowing us to reach CONNECTED state with just IPv4.
747 // If IPv6 setup failed in start_connect_() because the interface wasn't ready:
748 // - Bootup timing issues (#10281)
749 // - Cable unplugged/network interruption (#10705)
750 // We can now retry since we're in CONNECTED state and the interface is definitely up.
751 if (!this->ipv6_setup_done_) {
752 esp_err_t err = esp_netif_create_ip6_linklocal(this->eth_netif_);
753 if (err == ESP_OK) {
754 ESP_LOGD(TAG, "IPv6 link-local address created (retry succeeded)");
755 }
756 // Always set the flag to prevent continuous retries
757 // If IPv6 setup fails here with the interface up and stable, it's
758 // likely a persistent issue (IPv6 disabled at router, hardware
759 // limitation, etc.) that won't be resolved by further retries.
760 // The device continues to work with IPv4.
761 this->ipv6_setup_done_ = true;
762 }
763#endif /* USE_NETWORK_IPV6 */
764}
765
768#if USE_NETWORK_IPV6
770 this->ipv6_setup_done_ = false;
771#endif /* USE_NETWORK_IPV6 */
772 this->connect_begin_ = millis();
773 this->status_set_warning(LOG_STR("waiting for IP configuration"));
774
775 esp_err_t err;
776 err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str());
777 if (err != ERR_OK) {
778 ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err));
779 }
780
781 esp_netif_ip_info_t info;
782#ifdef USE_ETHERNET_MANUAL_IP
783 if (this->manual_ip_.has_value()) {
784 info.ip = this->manual_ip_->static_ip;
785 info.gw = this->manual_ip_->gateway;
786 info.netmask = this->manual_ip_->subnet;
787 } else
788#endif
789 {
790 info.ip.addr = 0;
791 info.gw.addr = 0;
792 info.netmask.addr = 0;
793 }
794
795 esp_netif_dhcp_status_t status = ESP_NETIF_DHCP_INIT;
796
797 err = esp_netif_dhcpc_get_status(this->eth_netif_, &status);
798 ESPHL_ERROR_CHECK(err, "DHCPC Get Status Failed!");
799
800 ESP_LOGV(TAG, "DHCP Client Status: %d", status);
801
802 err = esp_netif_dhcpc_stop(this->eth_netif_);
803 if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) {
804 ESPHL_ERROR_CHECK(err, "DHCPC stop error");
805 }
806
807 err = esp_netif_set_ip_info(this->eth_netif_, &info);
808 ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
809
810#ifdef USE_ETHERNET_MANUAL_IP
811 if (this->manual_ip_.has_value()) {
812 // Set DNS through esp_netif so the servers are stored in the netif's own
813 // dns[] array; raw dns_setserver() would be lost when the default-route
814 // arbitration re-applies the default netif's DNS.
815 // Log-only on failure: the link still has a working IP/gateway, so degraded
816 // name resolution does not justify marking the whole component failed.
817 esp_netif_dns_info_t dns{};
818 if (this->manual_ip_->dns1.is_set()) {
819 dns.ip = this->manual_ip_->dns1;
820 err = esp_netif_set_dns_info(this->eth_netif_, ESP_NETIF_DNS_MAIN, &dns);
821 if (err != ESP_OK) {
822 ESP_LOGE(TAG, "Set main DNS failed: %s", esp_err_to_name(err));
823 }
824 }
825 if (this->manual_ip_->dns2.is_set()) {
826 dns.ip = this->manual_ip_->dns2;
827 err = esp_netif_set_dns_info(this->eth_netif_, ESP_NETIF_DNS_BACKUP, &dns);
828 if (err != ESP_OK) {
829 ESP_LOGE(TAG, "Set backup DNS failed: %s", esp_err_to_name(err));
830 }
831 }
832 } else
833#endif
834 {
835 err = esp_netif_dhcpc_start(this->eth_netif_);
836 if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
837 ESPHL_ERROR_CHECK(err, "DHCPC start error");
838 }
839 }
840#if USE_NETWORK_IPV6
841 // Attempt to create IPv6 link-local address
842 // We MUST attempt this here, not just in finish_connect_(), because with
843 // min_ipv6_addr_count set, the component won't reach CONNECTED state without IPv6.
844 // However, this may fail with ESP_FAIL if the interface is not up yet:
845 // - At bootup when link isn't ready (#10281)
846 // - After disconnection/cable unplugged (#10705)
847 // We'll retry in finish_connect_() if it fails here.
848 err = esp_netif_create_ip6_linklocal(this->eth_netif_);
849 if (err != ESP_OK) {
850 if (err == ESP_ERR_ESP_NETIF_INVALID_PARAMS) {
851 // This is a programming error, not a transient failure
852 ESPHL_ERROR_CHECK(err, "esp_netif_create_ip6_linklocal invalid parameters");
853 } else {
854 // ESP_FAIL means the interface isn't up yet
855 // This is expected and non-fatal, happens in multiple scenarios:
856 // - During reconnection after network interruptions (#10705)
857 // - At bootup when the link isn't ready yet (#10281)
858 // We'll retry once we reach CONNECTED state and the interface is up
859 ESP_LOGW(TAG, "esp_netif_create_ip6_linklocal failed: %s", esp_err_to_name(err));
860 // Don't mark component as failed - this is a transient error
861 }
862 }
863#endif /* USE_NETWORK_IPV6 */
864
865 this->connect_begin_ = millis();
866 this->status_set_warning();
867}
868
870 if (!this->ethernet_initialized_) {
871 ESP_LOGCONFIG(TAG, " uninitialized/disabled");
872 return;
873 }
874 esp_netif_ip_info_t ip;
875 esp_netif_get_ip_info(this->eth_netif_, &ip);
876 const ip_addr_t *dns_ip1;
877 const ip_addr_t *dns_ip2;
878 {
880 dns_ip1 = dns_getserver(0);
881 dns_ip2 = dns_getserver(1);
882 }
883
884 // Use stack buffers for IP address formatting to avoid heap allocations
885 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
886 char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
887 char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
888 char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
889 char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
890 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
891 uint16_t link_speed = 10;
892 switch (this->get_link_speed()) {
893 case ETH_SPEED_100M:
894 link_speed = 100;
895 break;
896#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 1, 0)
897 case ETH_SPEED_1000M:
898 link_speed = 1000;
899 break;
900#endif
901 default:
902 break;
903 }
904 ESP_LOGCONFIG(TAG,
905 " IP Address: %s\n"
906 " Hostname: '%s'\n"
907 " Subnet: %s\n"
908 " Gateway: %s\n"
909 " DNS1: %s\n"
910 " DNS2: %s\n"
911 " MAC Address: %s\n"
912 " Is Full Duplex: %s\n"
913 " Link Speed: %u",
914 network::IPAddress(&ip.ip).str_to(ip_buf), App.get_name().c_str(),
915 network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf),
916 network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf),
917 this->get_eth_mac_address_pretty_into_buffer(mac_buf),
918 YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), link_speed);
919
920#if USE_NETWORK_IPV6
921 struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
922 uint8_t count = 0;
923 count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
924 assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
925 for (int i = 0; i < count; i++) {
926 ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i]));
927 }
928#endif /* USE_NETWORK_IPV6 */
929}
930
931#ifndef USE_ETHERNET_SPI
932void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
933#endif
934
936 if (!this->ethernet_initialized_) {
937 // External callers (mdns, ethernet_info, etc.) may ask for the MAC before/regardless
938 // of whether ethernet is enabled. Use the configured MAC if set, else the system ETH MAC.
939 if (this->fixed_mac_.has_value()) {
940 memcpy(mac, this->fixed_mac_->data(), MAC_ADDRESS_SIZE);
941 } else {
942 esp_read_mac(mac, ESP_MAC_ETH);
943 }
944 return;
945 }
946 esp_err_t err;
947 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac);
948 ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
949}
950
952 std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
953 uint8_t mac[MAC_ADDRESS_SIZE];
955 format_mac_addr_upper(mac, buf.data());
956 return buf.data();
957}
958
960 if (!this->ethernet_initialized_)
961 return ETH_DUPLEX_HALF;
962 esp_err_t err;
963 eth_duplex_t duplex_mode;
964 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
965 ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF);
966 return duplex_mode;
967}
968
970 if (!this->ethernet_initialized_)
971 return ETH_SPEED_10M;
972 esp_err_t err;
974 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
975 ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M);
976 return speed;
977}
978
980 ESP_LOGI(TAG, "Powering down ethernet PHY");
981 if (this->phy_ == nullptr) {
982 ESP_LOGE(TAG, "Ethernet PHY not assigned");
983 return false;
984 }
985 this->connected_ = false;
986 this->started_ = false;
987 // No need to enable_loop() here as this is only called during shutdown/reboot
988 if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) {
989 ESP_LOGE(TAG, "Error powering down ethernet PHY");
990 return false;
991 }
992 return true;
993}
994
995#ifndef USE_ETHERNET_SPI
996
997#ifdef USE_ETHERNET_KSZ8081
998constexpr uint8_t KSZ80XX_PC2R_REG_ADDR = 0x1F;
999
1001 esp_err_t err;
1002
1003 uint32_t phy_control_2;
1004 err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
1005 ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
1006#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
1007 char hex_buf[format_hex_pretty_size(PHY_REG_SIZE)];
1008#endif
1009 ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
1010
1011 /*
1012 * Bit 7 is `RMII Reference Clock Select`. Default is `0`.
1013 * KSZ8081RNA:
1014 * 0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode.
1015 * 1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
1016 * KSZ8081RND:
1017 * 0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
1018 * 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode.
1019 */
1020 if ((phy_control_2 & (1 << 7)) != (1 << 7)) {
1021 phy_control_2 |= 1 << 7;
1022 err = mac->write_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, phy_control_2);
1023 ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed");
1024 err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2));
1025 ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed");
1026 ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s",
1027 format_hex_pretty_to(hex_buf, (uint8_t *) &phy_control_2, PHY_REG_SIZE));
1028 }
1029}
1030#endif // USE_ETHERNET_KSZ8081
1031
1032void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) {
1033 esp_err_t err;
1034
1035#ifdef USE_ETHERNET_RTL8201
1036 constexpr uint8_t eth_phy_psr_reg_addr = 0x1F;
1037 if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
1038 ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page);
1039 err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page);
1040 ESPHL_ERROR_CHECK(err, "Select PHY Register page failed");
1041 }
1042#endif
1043
1044 ESP_LOGD(TAG, "Writing PHY reg 0x%02" PRIX32 " = 0x%04" PRIX32, register_data.address, register_data.value);
1045 err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value);
1046 ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
1047
1048#ifdef USE_ETHERNET_RTL8201
1049 if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
1050 ESP_LOGD(TAG, "Select PHY Register Page 0x00");
1051 err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
1052 ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
1053 }
1054#endif
1055}
1056
1057#ifdef USE_ETHERNET_YT8531
1059 esp_err_t err;
1060
1061 // The YT8531 disables auto-negotiation on hardware reset (undocumented behavior), and the
1062 // generic 802.3 driver only resets the PHY, so re-enable it (this also updates the driver's
1063 // cached auto-nego state used by esp_eth_start()).
1064 bool autoneg_enable = true;
1065 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_AUTONEGO, &autoneg_enable);
1066 ESPHL_ERROR_CHECK(err, "YT8531 enable auto-negotiation failed");
1067
1068 // RGMII needs ~2 ns Tx and Rx clock delays for reliable data sampling. These are set through
1069 // the YT8531 extended-register interface: write the ext-register address to 0x1E, then
1070 // read/modify/write its value via 0x1F.
1071 esp_eth_phy_reg_rw_data_t phy_reg;
1072 uint32_t reg_val;
1073 phy_reg.reg_value_p = &reg_val;
1074
1075 // RX ~2 ns coarse delay: EXT_CHIP_CONFIG (0xA001), set rxc_dly_en (bit 8).
1076 reg_val = 0xA001;
1077 phy_reg.reg_addr = 0x1E;
1078 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_WRITE_PHY_REG, &phy_reg);
1079 ESPHL_ERROR_CHECK(err, "YT8531 select Chip_Config failed");
1080 phy_reg.reg_addr = 0x1F;
1081 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_READ_PHY_REG, &phy_reg);
1082 ESPHL_ERROR_CHECK(err, "YT8531 read Chip_Config failed");
1083 reg_val |= (1U << 8);
1084 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_WRITE_PHY_REG, &phy_reg);
1085 ESPHL_ERROR_CHECK(err, "YT8531 write Chip_Config failed");
1086
1087 // TX ~2 ns delay: EXT_RGMII_CONFIG1 (0xA003), tx_delay_sel[3:0] and tx_delay_sel_fe[7:4] = 13.
1088 reg_val = 0xA003;
1089 phy_reg.reg_addr = 0x1E;
1090 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_WRITE_PHY_REG, &phy_reg);
1091 ESPHL_ERROR_CHECK(err, "YT8531 select RGMII_Config1 failed");
1092 phy_reg.reg_addr = 0x1F;
1093 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_READ_PHY_REG, &phy_reg);
1094 ESPHL_ERROR_CHECK(err, "YT8531 read RGMII_Config1 failed");
1095 reg_val = (reg_val & ~0x00FFU) | (13U << 4) | (13U << 0);
1096 err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_WRITE_PHY_REG, &phy_reg);
1097 ESPHL_ERROR_CHECK(err, "YT8531 write RGMII_Config1 failed");
1098}
1099#endif
1100
1101#endif
1102
1103} // namespace esphome::ethernet
1104
1105#endif // USE_ETHERNET && USE_ESP32
uint8_t status
Definition bl0942.h:8
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.
bool is_failed() const
Definition component.h:272
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void enable_loop()
Enable this component's loop.
Definition component.h:246
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:289
Helper class to lock the lwIP TCPIP core when making lwIP API calls from non-TCPIP threads.
Definition helpers.h:2020
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:461
std::vector< PHYRegister > phy_registers_
static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
void yt8531_phy_init_()
Apply YT8531-specific config: re-enable auto-negotiation (disabled on reset) and set the RGMII Tx/Rx ...
void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data)
Set arbitratry PHY registers from config.
static void got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
void log_error_and_mark_failed_(esp_err_t err, const char *message)
network::IPAddress get_dns_address(uint8_t num)
void add_phy_register(PHYRegister register_value)
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac)
Set RMII Reference Clock Select bit for KSZ8081.
optional< std::array< uint8_t, MAC_ADDRESS_SIZE > > fixed_mac_
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
const char * get_eth_mac_address_pretty_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
SPIInterface get_interface() const
Definition spi.h:362
const LogString * message
Definition component.cpp:35
eth_esp32_emac_config_t eth_esp32_emac_default_config(void)
int speed
Definition fan.h:3
constexpr uint8_t KSZ80XX_PC2R_REG_ADDR
void install_w5500_async_spi(eth_w5500_config_t &config)
esp_eth_phy_t * esp_eth_phy_new_jl1101(const eth_phy_config_t *config)
EthernetComponent * global_eth_component
std::array< IPAddress, 5 > IPAddresses
Definition ip_address.h:301
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:425
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1438
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
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:1505
static void uint32_t
char * str_to(char *buf) const
Definition ip_address.h:101
uint8_t event_id
Definition tt21100.cpp:3
SemaphoreHandle_t lock