这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » [ESP-IDF]小喵掌机基于IPV6的赛博木鱼

共1条 1/1 1 跳转至

[ESP-IDF]小喵掌机基于IPV6的赛博木鱼

高工
2026-08-15 16:25:19     打赏

简介

在上一篇文章IPV6在DIY和互联网上的应用在咱们论坛发布之后我终于想起一个比较好玩且具有交互性的小玩意,即赛博木鱼机。可以让所有的用户都参与进来。 原理是这样的:首先用ESP32连接到本地的WIFI上,然后想办法让ESP32来获取到一个IPV6的地址。之后再路由器中开启ESP32的IPV6地址,然后在ESP32上起一个HTTP server的服务。做一个敲木鱼的界面,然后当用户点击的时候会触发敲击木鱼的动画并且根据IP来统计敲击木鱼的次数。用来累计功德。这样的话就可以让所有人都参与进来了。截至到这个文章的发布的时候。木鱼的敲击次数已经到达了2000

image.png而每次用户的敲击都会触发我本地的ESP32的蜂鸣器,这样就达到了累计功德的效果(大笑)。

1d29000db5511d4ec08ce1dfd445d65c.jpg

实现思路如下

1- 首先新建一个ESP-IDF的基础的WIFI station的项目。

2- 在原本的项目中开启CONFIG_LWIP_IPV6_AUTOCONFIG 选项,因为它是默认关闭的。因为这里ESP-IDF 的 lwIP 虽然编译了 IPv6 支持(CONFIG_LWIP_IPV6=y),但 SLAAC 自动配置默认是关的。也就是说,即使路由器发 RA,ESP32 也不会自动生成全局 IPv6 地址。

3- 必须手动创建IPV6的链路:ESP-IDF 不会自动创建 fe80:: 链路本地地址。你必须在拿到 IPv4 后显式调用:

esp_netif_create_ip6_linklocal(e->esp_netif);

如果不调这个函数,整个 IPv6 协议栈就处于「虽然编译了但没启动」的状态。官方 example 里有这行,但 WiFi Station 的基础 example 里没有,很容易漏掉。

4- 更新的API函数:ESP-IDF v6.0.1 里 IPv6 事件叫 IP_EVENT_GOT_IP6,不是老版本的 IP_EVENT_STA_GOT_IP6。用老名字编译直接报 undeclared,这个坑让我查了半天头文件才找到。

详细代码如下所示

/* WiFi / IP 事件处理 */
static void wifi_event_handler(void *arg, esp_event_base_t base,
                                int32_t id, void *data)
{
    if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) {
        /* WiFi 已启动,开始连接 */
        esp_wifi_connect();
    } else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
        /* 连接断开,重试 */
        if (s_retry_num++ < EXAMPLE_ESP_MAXIMUM_RETRY)
            esp_wifi_connect();
        else
            xEventGroupSetBits(s_wifi_events, WIFI_FAIL_BIT);
    } else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
        /* 获取到 IPv4 地址,同时创建 IPv6 链路本地地址 */
        ip_event_got_ip_t *e = data;
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_events, WIFI_CONNECTED_BIT);
        ESP_ERROR_CHECK(esp_netif_create_ip6_linklocal(e->esp_netif));
    } else if (base == IP_EVENT && id == IP_EVENT_GOT_IP6) {
        /* 获取到 IPv6 地址(链路本地或 SLAAC 全局) */
        ip_event_got_ip6_t *e = data;
        ESP_LOGI(TAG, "IPv6: " IPV6STR, IPV62STR(e->ip6_info.ip));
    }
}

上述就是这个项目最核心的代码了。剩余的代码就是一个简单的HTTP 的server,并且界面是使用AI进行绘制的。当点击的时候可以触发POST请求,同时也支持GET请求。点击一次的话就会触发蜂鸣器的输出来达到一个敲木鱼的效果。

/* GET / — 返回主页 HTML */
static esp_err_t index_handler(httpd_req_t *req)
{
    httpd_resp_set_type(req, "text/html; charset=utf-8");
    httpd_resp_set_hdr(req, "Cache-Control", "no-store");
    return httpd_resp_sendstr(req, INDEX_HTML);
}

/* GET /favicon.ico — 返回空响应避免 404 日志 */
static esp_err_t favicon_handler(httpd_req_t *req)
{
    return httpd_resp_send(req, NULL, 0);
}

/* GET /api/state — 返回当前功德状态 JSON */
static esp_err_t state_handler(httpd_req_t *req)
{
    char ip[40];
    peer_ip(req, ip);

    /* 查找当前 IP 的个人功德 */
    uint32_t mine = 0;
    xSemaphoreTake(s_merit_mutex, portMAX_DELAY);
    for (int i = 0; i < s_merit.count; ++i)
        if (!strcmp(s_merit.entries[i].ip, ip))
            mine = s_merit.entries[i].merit;
    xSemaphoreGive(s_merit_mutex);

    send_state(req, ip, mine);
    return ESP_OK;
}

/* GET/POST /api/knock — both are accepted so cached pages and direct IPv6 tests work. */
static esp_err_t knock_handler(httpd_req_t *req)
{
    char ip[40];
    uint32_t mine;
    peer_ip(req, ip);

    /* 增加功德、蜂鸣、刷新 TFT、返回状态 */
    uint32_t total = merit_add(ip, &mine);
    buzzer_knock();
    tft_show_merit(total);
    send_state(req, ip, mine);
    return ESP_OK;
}

/* ==================== Web 服务器 ==================== */

/* 启动 HTTP 服务器,注册 URI 处理器 */
static void start_webserver(void)
{
    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.stack_size = 6144;  /* 增大栈空间以容纳 JSON 拼接 */
    ESP_ERROR_CHECK(httpd_start(&server, &config));

    const httpd_uri_t index   = {.uri = "/",          .method = HTTP_GET,  .handler = index_handler};
    const httpd_uri_t favicon = {.uri = "/favicon.ico",.method = HTTP_GET,  .handler = favicon_handler};
    const httpd_uri_t state   = {.uri = "/api/state",  .method = HTTP_GET,  .handler = state_handler};
    const httpd_uri_t knock_get  = {.uri = "/api/knock", .method = HTTP_GET,  .handler = knock_handler};
    const httpd_uri_t knock_post = {.uri = "/api/knock", .method = HTTP_POST, .handler = knock_handler};

    ESP_ERROR_CHECK(httpd_register_uri_handler(server, &index));
    ESP_ERROR_CHECK(httpd_register_uri_handler(server, &favicon));
    ESP_ERROR_CHECK(httpd_register_uri_handler(server, &state));
    ESP_ERROR_CHECK(httpd_register_uri_handler(server, &knock_get));
    ESP_ERROR_CHECK(httpd_register_uri_handler(server, &knock_post));

    ESP_LOGI(TAG, "HTTP 服务器已启动(支持 IPv6)");
}

如下为正常获取的IPV6的程序日志打印。

I (2288) wifi:connected with ImmortalWrt, aid = 3, channel 1, BW20, bssid = 50:33:f0:ee:bb:aa
I (2288) wifi:security: WPA2-PSK, phy: bgn, rssi: -59, cipher(pairwise:0x3, group:0x3), pmf:0
I (2298) wifi:pm start, type: 1

I (2298) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
I (2378) wifi:dp: 2, bi: 102400, li: 4, scale listen interval from 307200 us to 409600 us
I (2378) wifi:AP's beacon interval = 102400 us, DTIM period = 2
I (2488) wifi:<ba-add>idx:0 (ifx:0, 50:33:f0:ee:bb:aa), tid:0, ssn:1, winSize:64
I (3808) esp_netif_handlers: sta ip: 192.168.1.152, mask: 255.255.255.0, gw: 192.168.1.1
I (3808) wooden_fish: IPv6 HTTP server: http://[IPv6-address]/
I (3808) main_task: Returned from app_main()
I (5778) wooden_fish: IPv6: fe80:0000:0000:0000:8eaa:b5ff:feaf:860c
I (8778) wooden_fish: IPv6: 2001:0e68:545a:4b09:8eaa:b5ff:feaf:860c
I (8778) wooden_fish: IPv6: fd9d:bb76:ac5c:0000:8eaa:b5ff:feaf:860c

需要注意的是FE80开头的为本地的IPV6地址,如果要分享给别人的话不要分享FE80开头的地址。否则可能会出现无法访问的情况。同时由于国内的运营商显示,好像没办法开启80端口,所以在配置路由器的防火墙的设置的时候避免使用80端口作为服务的端口输出。

image.png

游戏地址:http://[2001:e68:545a:4b09:8eaa:b5ff:feaf:860c]/

大家一起来敲木鱼吧!


视频效果:


总结

这个游戏配合IPV6来玩还是非常有趣的,多分享一些人的话这个赛博木鱼会一直在响。每个人都有一个ip地址的话家里的的IOT设备可玩性就会变得超级高。大家一起玩起来吧!




关键词: ESP-IDF     小喵掌机     ESP32     赛博木鱼    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]