388 lines
15 KiB
C++
388 lines
15 KiB
C++
/**
|
|
* @file network.cpp
|
|
* @brief W5500 Ethernet — init, DHCP, IPv6, event handlers
|
|
*
|
|
* Manages the W5500 Ethernet controller on SPI2_HOST. Provides network
|
|
* status strings (IP, gateway, DNS, MAC) for use by diagnostics and display.
|
|
*
|
|
* Pin mapping:
|
|
* W5500_SCK = GPIO12 W5500_MOSI = GPIO11
|
|
* W5500_MISO = GPIO13 W5500_CS = GPIO10
|
|
* W5500_RST = GPIO9 W5500_INT = GPIO14
|
|
*
|
|
* NOTE: The LCD uses separate SPI3_HOST (GPIO40/41), NOT the same bus.
|
|
*/
|
|
|
|
#include "network.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "esp_log.h"
|
|
#include "esp_event.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_mac.h"
|
|
#include "driver/spi_master.h"
|
|
#include "esp_eth.h"
|
|
#include "esp_eth_mac.h"
|
|
#include "esp_eth_phy.h"
|
|
#include "esp_eth_com.h"
|
|
#include "esp_eth_mac_w5500.h"
|
|
#include "esp_eth_phy_w5500.h"
|
|
#include "lwip/priv/nd6_priv.h"
|
|
|
|
static const char *TAG = "NETWORK";
|
|
|
|
// ─── Pin Mapping ────────────────────────────────────────────────────────────
|
|
|
|
#define PIN_SCK 12
|
|
#define PIN_MOSI 11
|
|
#define PIN_MISO 13
|
|
#define PIN_W5500_CS 10
|
|
#define PIN_W5500_RST 9
|
|
#define PIN_W5500_INT 14
|
|
|
|
// ─── Global state ───────────────────────────────────────────────────────────
|
|
|
|
static esp_eth_handle_t s_eth_handle = NULL;
|
|
|
|
static bool s_link_up = false;
|
|
static bool s_has_ipv4 = false;
|
|
static bool s_has_ipv6 = false;
|
|
|
|
// Netif handle (store so we can enable IPv6 later)
|
|
static esp_netif_t *s_netif = NULL;
|
|
|
|
static char s_ipv4_str[24] = "--";
|
|
static char s_netmask_str[24] = "--";
|
|
static char s_gw_str[24] = "--";
|
|
static char s_dns_str[24] = "--";
|
|
// Separate storage for link-local and global IPv6 addresses
|
|
static char s_ipv6_ll_str[48] = "--"; // fe80::...
|
|
static char s_ipv6_gua_str[48] = "--"; // 2a02::... (global unicast)
|
|
static char s_gw6_str[48] = "--"; // IPv6 default gateway (link-local of router)
|
|
static char s_dns6_str[48] = "--"; // IPv6 DNS from RDNSS
|
|
static char s_mac_str[20] = "--";
|
|
|
|
// Track whether we have specific IPv6 address types
|
|
static bool s_has_ipv6_ll = false;
|
|
static bool s_has_ipv6_gua = false;
|
|
|
|
// Backward-compat: s_ipv6_str points to whichever is more interesting
|
|
static const char *s_ipv6_str = s_ipv6_ll_str;
|
|
|
|
// ─── IPv6 Compact Formatter ───────────────────────────────────────────────
|
|
|
|
/// Format an IPv6 address in RFC 5952 compact form (with ::) for display.
|
|
/// Uses esp_netif's block macros which apply htonl for correct endianness.
|
|
static void format_ipv6_compact(const esp_ip6_addr_t *ip6, char *buf, size_t bufsz)
|
|
{
|
|
// Get the 8 16-bit blocks with correct byte order
|
|
uint16_t blocks[8];
|
|
blocks[0] = ESP_IP6_ADDR_BLOCK1(ip6);
|
|
blocks[1] = ESP_IP6_ADDR_BLOCK2(ip6);
|
|
blocks[2] = ESP_IP6_ADDR_BLOCK3(ip6);
|
|
blocks[3] = ESP_IP6_ADDR_BLOCK4(ip6);
|
|
blocks[4] = ESP_IP6_ADDR_BLOCK5(ip6);
|
|
blocks[5] = ESP_IP6_ADDR_BLOCK6(ip6);
|
|
blocks[6] = ESP_IP6_ADDR_BLOCK7(ip6);
|
|
blocks[7] = ESP_IP6_ADDR_BLOCK8(ip6);
|
|
|
|
// Find longest run of consecutive zero blocks (RFC 5952: replace with ::)
|
|
int best_start = -1, best_len = 0;
|
|
int cur_start = -1, cur_len = 0;
|
|
for (int i = 0; i < 8; i++) {
|
|
if (blocks[i] == 0) {
|
|
if (cur_start < 0) { cur_start = i; cur_len = 1; }
|
|
else { cur_len++; }
|
|
if (cur_len > best_len) { best_start = cur_start; best_len = cur_len; }
|
|
} else {
|
|
cur_start = -1; cur_len = 0;
|
|
}
|
|
}
|
|
|
|
size_t pos = 0;
|
|
for (int i = 0; i < 8; i++) {
|
|
if (best_len >= 2 && i == best_start) {
|
|
// Insert :: (only once at the start of the zero run)
|
|
if (pos + 2 < bufsz) { buf[pos++] = ':'; buf[pos++] = ':'; }
|
|
i += best_len - 1; // skip the rest of the zero run
|
|
continue;
|
|
}
|
|
if (pos > 0 && pos < bufsz && buf[pos-1] != ':') {
|
|
// If we just wrote a block (not after ::), add colon separator
|
|
// But only if we didn't just write ':' from the previous iteration
|
|
if (pos + 1 < bufsz) buf[pos++] = ':';
|
|
}
|
|
// Write the hex block without leading zeros
|
|
int cnt = snprintf(buf + pos, bufsz - pos, "%x", (unsigned)blocks[i]);
|
|
if (cnt > 0 && (size_t)cnt < bufsz - pos) pos += cnt;
|
|
}
|
|
// Handle edge case: all-zero address (::)
|
|
if (best_len == 8) {
|
|
buf[0] = ':'; buf[1] = ':'; pos = 2;
|
|
}
|
|
buf[pos < bufsz ? pos : bufsz - 1] = '\0';
|
|
}
|
|
|
|
// ─── Event Handlers ─────────────────────────────────────────────────────────
|
|
|
|
static void eth_event_handler(void *arg, esp_event_base_t event_base,
|
|
int32_t event_id, void *event_data)
|
|
{
|
|
switch (event_id) {
|
|
|
|
case ETHERNET_EVENT_CONNECTED:
|
|
ESP_LOGI(TAG, "Ethernet LINK UP");
|
|
s_link_up = true;
|
|
break;
|
|
|
|
case ETHERNET_EVENT_DISCONNECTED:
|
|
ESP_LOGI(TAG, "Ethernet LINK DOWN");
|
|
s_link_up = false;
|
|
s_has_ipv4 = false;
|
|
s_has_ipv6 = false;
|
|
break;
|
|
|
|
case ETHERNET_EVENT_START:
|
|
ESP_LOGI(TAG, "Ethernet START");
|
|
break;
|
|
|
|
case ETHERNET_EVENT_STOP:
|
|
ESP_LOGI(TAG, "Ethernet STOP");
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void got_ip_event_handler(void *arg, esp_event_base_t event_base,
|
|
int32_t event_id, void *event_data)
|
|
{
|
|
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
|
|
|
s_has_ipv4 = true;
|
|
|
|
snprintf(s_ipv4_str, sizeof(s_ipv4_str),
|
|
IPSTR, IP2STR(&event->ip_info.ip));
|
|
snprintf(s_gw_str, sizeof(s_gw_str),
|
|
IPSTR, IP2STR(&event->ip_info.gw));
|
|
snprintf(s_netmask_str, sizeof(s_netmask_str),
|
|
IPSTR, IP2STR(&event->ip_info.netmask));
|
|
|
|
// Get DNS from DHCP
|
|
esp_netif_t *netif = event->esp_netif;
|
|
// Find the first IPv4 DNS slot (RDNSS may have overwritten MAIN with IPv6)
|
|
esp_netif_dns_info_t dns;
|
|
esp_netif_dns_type_t slots[] = {
|
|
ESP_NETIF_DNS_MAIN, ESP_NETIF_DNS_BACKUP, ESP_NETIF_DNS_FALLBACK
|
|
};
|
|
for (int i = 0; i < 3; i++) {
|
|
if (esp_netif_get_dns_info(netif, slots[i], &dns) == ESP_OK &&
|
|
dns.ip.type == ESP_IPADDR_TYPE_V4) {
|
|
snprintf(s_dns_str, sizeof(s_dns_str),
|
|
IPSTR, IP2STR(&dns.ip.u_addr.ip4));
|
|
break;
|
|
}
|
|
}
|
|
|
|
ESP_LOGI(TAG, "IP: %s GW: %s MASK: %s DNS: %s",
|
|
s_ipv4_str, s_gw_str, s_netmask_str, s_dns_str);
|
|
}
|
|
|
|
static void got_ip6_event_handler(void *arg, esp_event_base_t event_base,
|
|
int32_t event_id, void *event_data)
|
|
{
|
|
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
|
|
|
|
s_has_ipv6 = true;
|
|
s_netif = event->esp_netif;
|
|
|
|
// Determine address type (link-local vs global)
|
|
esp_ip6_addr_type_t addr_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip);
|
|
char temp_buf[48];
|
|
format_ipv6_compact(&event->ip6_info.ip, temp_buf, sizeof(temp_buf));
|
|
|
|
if (addr_type == ESP_IP6_ADDR_IS_LINK_LOCAL) {
|
|
// Link-local: fe80::...
|
|
strncpy(s_ipv6_ll_str, temp_buf, sizeof(s_ipv6_ll_str) - 1);
|
|
s_ipv6_ll_str[sizeof(s_ipv6_ll_str) - 1] = '\0';
|
|
s_has_ipv6_ll = true;
|
|
ESP_LOGI(TAG, "IPv6 link-local: %s", s_ipv6_ll_str);
|
|
} else if (addr_type == ESP_IP6_ADDR_IS_GLOBAL) {
|
|
// Global unicast: 2xxx:: or other GUA prefixes
|
|
strncpy(s_ipv6_gua_str, temp_buf, sizeof(s_ipv6_gua_str) - 1);
|
|
s_ipv6_gua_str[sizeof(s_ipv6_gua_str) - 1] = '\0';
|
|
s_has_ipv6_gua = true;
|
|
s_ipv6_str = s_ipv6_gua_str; // point backward-compat to GUA
|
|
ESP_LOGI(TAG, "IPv6 global: %s", s_ipv6_gua_str);
|
|
} else {
|
|
// Other types (ULA, etc.) - just store in main field
|
|
strncpy(s_ipv6_ll_str, temp_buf, sizeof(s_ipv6_ll_str) - 1);
|
|
s_ipv6_ll_str[sizeof(s_ipv6_ll_str) - 1] = '\0';
|
|
s_has_ipv6_ll = true;
|
|
}
|
|
|
|
// Capture IPv6 DNS from RDNSS option in RA
|
|
esp_netif_dns_info_t dns;
|
|
if (s_netif && esp_netif_get_dns_info(s_netif, ESP_NETIF_DNS_MAIN, &dns) == ESP_OK) {
|
|
if (dns.ip.type == ESP_IPADDR_TYPE_V6) {
|
|
format_ipv6_compact(&dns.ip.u_addr.ip6, s_dns6_str, sizeof(s_dns6_str));
|
|
ESP_LOGI(TAG, "IPv6 DNS: %s", s_dns6_str);
|
|
}
|
|
}
|
|
|
|
// Extract IPv6 default gateway from lwIP's ND6 default router list
|
|
for (int i = 0; i < LWIP_ND6_NUM_ROUTERS; i++) {
|
|
struct nd6_neighbor_cache_entry *ne = default_router_list[i].neighbor_entry;
|
|
if (ne != NULL && !ip6_addr_isany(&ne->next_hop_address)) {
|
|
// Found a valid router — its next_hop_address is the gateway
|
|
format_ipv6_compact((const esp_ip6_addr_t *)&ne->next_hop_address,
|
|
s_gw6_str, sizeof(s_gw6_str));
|
|
ESP_LOGI(TAG, "IPv6 gateway: %s", s_gw6_str);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ─── SPI Bus ────────────────────────────────────────────────────────────────
|
|
|
|
void network_spi_init(void)
|
|
{
|
|
spi_bus_config_t buscfg;
|
|
memset(&buscfg, 0, sizeof(buscfg));
|
|
buscfg.mosi_io_num = (int)PIN_MOSI;
|
|
buscfg.miso_io_num = (int)PIN_MISO;
|
|
buscfg.sclk_io_num = (int)PIN_SCK;
|
|
buscfg.quadwp_io_num = -1;
|
|
buscfg.quadhd_io_num = -1;
|
|
buscfg.data4_io_num = -1;
|
|
buscfg.data5_io_num = -1;
|
|
buscfg.data6_io_num = -1;
|
|
buscfg.data7_io_num = -1;
|
|
buscfg.max_transfer_sz = 0;
|
|
buscfg.flags = 0;
|
|
buscfg.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO;
|
|
buscfg.intr_flags = 0;
|
|
|
|
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
|
ESP_LOGI(TAG, "SPI2_HOST initialized for W5500 Ethernet");
|
|
}
|
|
|
|
// ─── Ethernet Init ──────────────────────────────────────────────────────────
|
|
|
|
esp_eth_handle_t network_eth_init(void)
|
|
{
|
|
ESP_LOGI(TAG, "Starting W5500 Ethernet...");
|
|
|
|
// SPI device config
|
|
spi_device_interface_config_t spi_devcfg;
|
|
memset(&spi_devcfg, 0, sizeof(spi_devcfg));
|
|
spi_devcfg.command_bits = 16;
|
|
spi_devcfg.address_bits = 8;
|
|
spi_devcfg.mode = 0;
|
|
spi_devcfg.clock_speed_hz = 5 * 1000 * 1000; // 5 MHz
|
|
spi_devcfg.spics_io_num = (int)PIN_W5500_CS;
|
|
spi_devcfg.queue_size = 20;
|
|
|
|
// W5500 config
|
|
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(SPI2_HOST, &spi_devcfg);
|
|
w5500_config.int_gpio_num = (int)PIN_W5500_INT;
|
|
|
|
// MAC / PHY config
|
|
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
|
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
|
phy_config.reset_gpio_num = (int)PIN_W5500_RST;
|
|
|
|
// Create MAC + 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);
|
|
|
|
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy);
|
|
|
|
ESP_LOGI(TAG, "Installing Ethernet driver...");
|
|
ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, &s_eth_handle));
|
|
ESP_LOGI(TAG, "Ethernet driver installed");
|
|
|
|
// Set MAC address from ESP32 efuse (W5500 has no factory MAC)
|
|
uint8_t mac_addr[6];
|
|
esp_read_mac(mac_addr, ESP_MAC_ETH);
|
|
// Ensure locally-administered unicast: set bit 1 of byte 0
|
|
mac_addr[0] = (mac_addr[0] & 0xFE) | 0x02;
|
|
esp_eth_ioctl(s_eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr);
|
|
|
|
// Store MAC string
|
|
snprintf(s_mac_str, sizeof(s_mac_str),
|
|
"%02X:%02X:%02X:%02X:%02X:%02X",
|
|
mac_addr[0], mac_addr[1], mac_addr[2],
|
|
mac_addr[3], mac_addr[4], mac_addr[5]);
|
|
ESP_LOGI(TAG, "Ethernet MAC: %s", s_mac_str);
|
|
|
|
return s_eth_handle;
|
|
}
|
|
|
|
esp_netif_t *network_netif_init(esp_eth_handle_t eth_handle)
|
|
{
|
|
esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH();
|
|
s_netif = esp_netif_new(&netif_cfg);
|
|
ESP_ERROR_CHECK(esp_netif_attach(s_netif, esp_eth_new_netif_glue(eth_handle)));
|
|
return s_netif;
|
|
}
|
|
|
|
void network_start_ipv6(void)
|
|
{
|
|
if (!s_netif) {
|
|
ESP_LOGW(TAG, "Cannot start IPv6: netif not ready");
|
|
return;
|
|
}
|
|
// Explicitly request IPv6 link-local address creation.
|
|
// In ESP-IDF v6, this is NOT automatic for Ethernet interfaces.
|
|
// This triggers SLAAC and fires IP_EVENT_GOT_IP6 when an address is assigned.
|
|
esp_err_t ret = esp_netif_create_ip6_linklocal(s_netif);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGW(TAG, "Failed to create IPv6 link-local: %s", esp_err_to_name(ret));
|
|
} else {
|
|
ESP_LOGI(TAG, "IPv6 link-local creation requested");
|
|
}
|
|
}
|
|
|
|
void network_eth_start(esp_eth_handle_t eth_handle)
|
|
{
|
|
ESP_ERROR_CHECK(esp_eth_start(eth_handle));
|
|
ESP_LOGI(TAG, "W5500 started, waiting for link + DHCP...");
|
|
}
|
|
|
|
void network_register_handlers(void)
|
|
{
|
|
esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID,
|
|
ð_event_handler, NULL);
|
|
esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP,
|
|
&got_ip_event_handler, NULL);
|
|
esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6,
|
|
&got_ip6_event_handler, NULL);
|
|
}
|
|
|
|
// ─── Accessors ──────────────────────────────────────────────────────────────
|
|
|
|
bool network_is_link_up(void) { return s_link_up; }
|
|
bool network_has_ipv4(void) { return s_has_ipv4; }
|
|
bool network_has_ipv6(void) { return s_has_ipv6; }
|
|
bool network_has_ipv6_ll(void) { return s_has_ipv6_ll; }
|
|
bool network_has_ipv6_gua(void) { return s_has_ipv6_gua; }
|
|
|
|
const char *network_get_ipv4_str(void) { return s_ipv4_str; }
|
|
const char *network_get_gateway_str(void) { return s_gw_str; }
|
|
const char *network_get_netmask_str(void) { return s_netmask_str; }
|
|
const char *network_get_dns_str(void) { return s_dns_str; }
|
|
const char *network_get_ipv6_str(void) { return s_ipv6_str; }
|
|
const char *network_get_ipv6_ll_str(void) { return s_ipv6_ll_str; }
|
|
const char *network_get_ipv6_gua_str(void){ return s_ipv6_gua_str; }
|
|
const char *network_get_gateway6_str(void) { return s_gw6_str; }
|
|
const char *network_get_dns6_str(void) { return s_dns6_str; }
|
|
const char *network_get_mac_str(void) { return s_mac_str; }
|
|
|
|
int network_get_eth_scope_id(void)
|
|
{
|
|
if (!s_netif) return 0;
|
|
return esp_netif_get_netif_impl_index(s_netif);
|
|
}
|