1. 所需的头文件

您需要在代码文件的顶部包含以下三个头文件,才能使用所有相关的底层函数。

#include <esp_netif.h>      // 用于访问底层的网络接口 (netif)
#include "lwip/sockets.h"  // 用于访问网络套接字函数,如此处的 ntohl()
#include <WiFi.h>          // 用于启用WiFi和IPv6功能

2. IPv6 地址格式化与获取函数

这是解决问题的核心。由于标准库函数不可用,我们手动创建了 formatIPv6 函数,然后由 getIPv6 调用它。

/**
 * @brief 手动将底层的 esp_ip6_addr_t 结构体格式化为人类可读的字符串。
 * @param addr 指向IPv6地址结构体的指针。
 * @return 格式化后的IPv6地址字符串。
 */
String formatIPv6(const esp_ip6_addr_t *addr) {
  if (addr == nullptr) {
    return String("::");
  }
  char buf[40];
  
  // ESP32是小端序,网络字节序是大端序,需要转换才能正确拼接。
  uint32_t word0 = ntohl(addr->addr[0]);
  uint32_t word1 = ntohl(addr->addr[1]);
  uint32_t word2 = ntohl(addr->addr[2]);
  uint32_t word3 = ntohl(addr->addr[3]);
  
  // 使用 snprintf 安全地将4个32位整数格式化为8个16位的十六进制数
  snprintf(buf, sizeof(buf), "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
           (uint16_t)(word0 >> 16),
           (uint16_t)(word0 & 0xFFFF),
           (uint16_t)(word1 >> 16),
           (uint16_t)(word1 & 0xFFFF),
           (uint16_t)(word2 >> 16),
           (uint16_t)(word2 & 0xFFFF),
           (uint16_t)(word3 >> 16),
           (uint16_t)(word3 & 0xFFFF)
  );
  
  return String(buf);
}

/**
 * @brief 获取设备的IPv6地址。
 * @return 返回IPv6地址字符串。如果获取失败,则返回 "Not Available"。
 */
String getIPv6() {
  // 获取WiFi STA网络接口的句柄
  esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
  if (!netif) return String("Not Available");

  esp_ip6_addr_t addr;
  
  // 优先尝试获取全局IPv6地址 (2000::/3),这是公网可路由的
  if (esp_netif_get_ip6_global(netif, &addr) == ESP_OK) {
      // 检查地址是否不为全0
      if (addr.addr[0] != 0 || addr.addr[1] != 0 || addr.addr[2] != 0 || addr.addr[3] != 0) {
        return formatIPv6(&addr); // 使用我们的手动格式化函数
      }
  }
  
  // 如果没有全局地址,则获取本地链路地址 (fe80::/10),仅在局域网内有效
  if (esp_netif_get_ip6_linklocal(netif, &addr) == ESP_OK) {
    if (addr.addr[0] != 0 || addr.addr[1] != 0 || addr.addr[2] != 0 || addr.addr[3] != 0) {
        return formatIPv6(&addr); // 使用我们的手动格式化函数
    }
  }

  return String("Not Available");
}

3. 在 setup() 函数中的调用示例

在连接WiFi成功后,您需要先启用IPv6,然后就可以调用 getIPv6() 函数了。

void setup() {
  // ... 其他初始化代码 ...

  // 连接WiFi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi 已连接!");

  // 启用IPv6功能
  WiFi.enableIPv6(); 
  
  // 稍微等待一下,让网络协议栈有时间获取到地址
  delay(1000); 
  
  // 打印IPv4和IPv6地址
  Serial.print("IPv4 地址: "); 
  Serial.println(WiFi.localIP());
  Serial.print("IPv6 地址: "); 
  Serial.println(getIPv6()); // 调用我们的函数

  // ... 启动Web服务器等 ...
}

4. 在 Web 服务器中的应用示例

您可以创建一个专门的路由来获取IPv6地址,并在系统信息中包含它。

// Web服务器路由处理函数
void handleGetIPv6() {
  server.send(200, "text/plain", getIPv6());
}

// 系统信息JSON生成函数
String getSystemInfoJSON() {
  String j = "{";
  // ... 其他系统信息 ...
  j += "\"ipv4\":\"" + WiFi.localIP().toString() + "\",";
  j += "\"ipv6\":\"" + getIPv6() + "\""; // 在JSON中包含IPv6地址
  j += "}";
  return j;
}

void setup() {
    // ...
    // 在setup()中设置Web路由
    server.on("/ipv6", HTTP_GET, handleGetIPv6);
    server.on("/sysinfo", HTTP_GET, handleSysInfo);
    // ...
}

如图所示

image.png 可以进行ipv6 ddns