inet-mon/firmware/main/network.cpp
Ricardo (XenGi) Band 840909af66
nearly done
2026-07-17 00:08:48 +02:00

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(&eth_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,
&eth_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);
}