ESP32-WIFI-WebUI控制LED

ESP32-WIFI-WebUI控制LED

逻辑流程(A = ESP32,B = 客户端)

  1. A :将 ESP32 配置为 AP 模式,开启 Wi-Fi 热点并作为 HTTP 服务器运行。
  2. B:电脑或手机连接到 ESP32 热点,建立局域网通信。
  3. B → A :客户端在浏览器访问 192.168.4.1,向 ESP32 发送 HTTP GET 请求。
  4. A → B:ESP32 响应请求,返回 HTML 网页文件。
  5. B:浏览器接收并解析 HTML 文件,显示网页内容。
  6. B → A :用户在网页上点击按钮,浏览器向 ESP32 发送带有控制参数的 HTTP GET 请求(如 led_on / led_off)。
  7. A:ESP32 解析 URL 参数,根据指令触发对应的回调函数执行操作(如控制 LED 开关)。

Web界面

通过手机抓包软件分析

第一次进入192.168.4.1网址

可以看到响应内容格式为

text/html

对应代码段中处理根目录请求函数构造的数据响应格式(text/html)

按下按键(触发发送led_on事件) 按下按键(触发发送led_off事件)

可以分析看到请求路径为/led

分析代码可知触发路径为/led的请求格式后,处理函数为led_handler

后面源码中有详细解释led_handler()作用

在此简略说明处理过程:获取URL,查询键值对,判断值,做出处理

重要代码段分析

初始化NVS

c 复制代码
    // 初始化NVS(非易失性存储)闪存,用于存储WiFi配置等数据
            ESP_ERROR_CHECK(nvs_flash_init());

初始化 Wi-Fi AP 模式

c 复制代码
            void wifi_init_softap(void)
        {
            // 初始化网络接口
            ESP_ERROR_CHECK(esp_netif_init());
            // 创建默认事件循环
            ESP_ERROR_CHECK(esp_event_loop_create_default());
            // 创建默认的Wi-Fi AP网络接口
            esp_netif_create_default_wifi_ap();
            // 初始化Wi-Fi配置
            wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
            ESP_ERROR_CHECK(esp_wifi_init(&cfg));
            // 配置Wi-Fi AP参数
            wifi_config_t wifi_config = {
                .ap = {
                    .ssid = "ESP32_WEB", // 设置SSID名称
                    .ssid_len = strlen("ESP32_WEB"), // SSID长度
                    .password = "12345678", // 设置密码
                    .max_connection = 2, // 最大连接数
                    .authmode = WIFI_AUTH_WPA_WPA2_PSK // 设置认证模式为WPA/WPA2
                },
            };
            // 如果密码为空,则将认证模式设置为开放模式
            if (strlen((char *)wifi_config.ap.password) == 0)
                wifi_config.ap.authmode = WIFI_AUTH_OPEN;
            // 设置Wi-Fi模式为AP模式
            ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
            // 设置Wi-Fi配置
            ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
            // 启动Wi-Fi
            ESP_ERROR_CHECK(esp_wifi_start());
            // 打印日志,显示Wi-Fi AP已启动的信息
            ESP_LOGI(TAG, "Wi-Fi AP started. SSID:%s password:%s", "ESP32_WEB", "12345678");
        }

初始化外设led(略)

启动HTTP服务器(注册服务器响应函数)

c 复制代码
   // 启动 HTTP 服务器
        httpd_handle_t start_webserver(void)
        {
            httpd_config_t config = HTTPD_DEFAULT_CONFIG();
            httpd_handle_t server = NULL;
            
            if (httpd_start(&server, &config) == ESP_OK) {
                //注册根路径(/)的处理函数:
                httpd_uri_t index_uri = {
                    .uri       = "/",
                    .method    = HTTP_GET,
                    .handler   = index_handler,// 设置处理函数为index_handler
                    .user_ctx  = NULL           //回传html
                };
                httpd_register_uri_handler(server, &index_uri);//注册到服务器中
                //注册 /led 路径的处理函数
                // 定义一个httpd_uri_t类型的变量led_uri// 设置uri为"/led"
                httpd_uri_t led_uri = {
                    .uri       = "/led",
                    .method    = HTTP_GET,
                    .handler   = led_handler,// 设置处理函数为led_handler
                    .user_ctx  = NULL           //设置led 
                };
                httpd_register_uri_handler(server, &led_uri);
            }

            return server;
        }

处理/根目录请求,返回 HTML

复制代码
static esp_err_t index_handler(httpd_req_t *req)------
        {
            httpd_resp_set_type(req, "text/html");
            return httpd_resp_send(req, (const char *)_binary_index_html_start,
                                _binary_index_html_end - _binary_index_html_start);
        }

处理 /led 控制请求

c 复制代码
        // 静态函数,用于处理LED控制请求
        static esp_err_t led_handler(httpd_req_t *req)
        {
            // 定义一个字符数组,用于存储URL查询字符串
            char buf[100];
            // 获取URL查询字符串的长度,并加1
            size_t len = httpd_req_get_url_query_len(req) + 1;
            // 如果URL查询字符串的长度大于1
            if (len > 1) {
                // 获取URL查询字符串
                httpd_req_get_url_query_str(req, buf, len);

                // 定义一个字符数组,用于存储查询参数
                char param[10];
                // 如果查询参数为"state"
                if (httpd_query_key_value(buf, "state", param, sizeof(param)) == ESP_OK) {
                    // 打印查询参数
                    ESP_LOGI(TAG, "LED state param: %s", param);
                    // 如果查询参数为"on"
                    if (strcmp(param, "on") == 0) {
                        // 设置LED引脚为高电平
                        gpio_set_level(LED_GPIO, 1);
                        // 设置LED状态为打开
                        led_on = true;
                        // 发送响应,LED已打开
                        httpd_resp_sendstr(req, "LED turned ON");
                    // 如果查询参数为"off"
                    } else if (strcmp(param, "off") == 0) {
                        // 设置LED引脚为低电平
                        gpio_set_level(LED_GPIO, 0);
                        // 设置LED状态为关闭
                        led_on = false;
                        // 发送响应,LED已关闭
                        httpd_resp_sendstr(req, "LED turned OFF");
                    // 如果查询参数不为"on"或"off"
                    } else {
                        // 发送响应,LED状态无效
                        httpd_resp_sendstr(req, "Invalid LED state");
                    }
                // 如果查询参数不为"state"
                } else {
                    // 发送响应,缺少"state"参数
                    httpd_resp_sendstr(req, "Missing 'state' param");
                }
            // 如果URL查询字符串的长度不大于1
            } else {
                // 发送响应,未提供查询参数
                httpd_resp_sendstr(req, "No query provided");
            }

            // 返回ESP_OK
            return ESP_OK;
        }

Web网页端(HTML)

触发部分

html 复制代码
  <script>
      let ledOn = false; //定义变量,用于存储LED状态
      function toggleLED() {
        // 切换LED状态函数
        ledOn = !ledOn;
        //更新网页上显示的LED状态:
        document.getElementById("led-status").innerText =
          "当前状态: " + (ledOn ? "开启" : "关闭");

        // 向ESP32发送HTTP GET请求(根据固件的URL接口设计修改)
        //请求的URL为 /led?state=on 或 /led?state=off,根据 ledOn 的值决定:
        fetch("/led?state=" + (ledOn ? "on" : "off"))
          //  then 方法处理ESP32的响应:
          // 检查响应是否成功(response.ok)。
          // 如果响应失败,抛出一个错误,提示"网络错误"。
          // 如果成功,调用 response.text() 将响应内容转换为文本。
          .then((response) => {
            if (!response.ok) {
              throw new Error("网络错误");
            }
            return response.text();
          })
          .then((data) => {
            console.log("ESP响应:", data);
            //打印ESP32返回的响应数据到浏览器的控制台,便于调试。
          })
          .catch((error) => {
            alert("请求失败: " + error);
            //捕获错误并弹出一个警告框,显示错误信息
          });
      }
    </script>

源码:

main.c

c 复制代码
    #include <string.h>
    #include "esp_event.h"
    #include "esp_log.h"
    #include "esp_netif.h"
    #include "nvs_flash.h"
    #include "esp_wifi.h"
    #include "esp_http_server.h"
    #include "driver/gpio.h"

    #define LED_GPIO GPIO_NUM_48 // 可根据板子修改,比如GPIO2或GPIO5
    static const char *TAG = "WEB_LED";

    // 嵌入的 HTML 网页内容
    extern const uint8_t _binary_index_html_start[];
    extern const uint8_t _binary_index_html_end[];

    // LED 控制状态
    static bool led_on = false;

    // 处理根目录请求,返回 HTML
    static esp_err_t index_handler(httpd_req_t *req)
    {
        httpd_resp_set_type(req, "text/html");
        return httpd_resp_send(req, (const char *)_binary_index_html_start,
                            _binary_index_html_end - _binary_index_html_start);
    }

    // 处理 /led 控制请求
    // 静态函数,用于处理LED控制请求
    static esp_err_t led_handler(httpd_req_t *req)
    {
        // 定义一个字符数组,用于存储URL查询字符串
        char buf[100];
        // 获取URL查询字符串的长度,并加1
        size_t len = httpd_req_get_url_query_len(req) + 1;
        // 如果URL查询字符串的长度大于1
        if (len > 1) {
            // 获取URL查询字符串
            httpd_req_get_url_query_str(req, buf, len);

            // 定义一个字符数组,用于存储查询参数
            char param[10];
            // 如果查询参数为"state"
            if (httpd_query_key_value(buf, "state", param, sizeof(param)) == ESP_OK) {
                // 打印查询参数
                ESP_LOGI(TAG, "LED state param: %s", param);
                // 如果查询参数为"on"
                if (strcmp(param, "on") == 0) {
                    // 设置LED引脚为高电平
                    gpio_set_level(LED_GPIO, 1);
                    // 设置LED状态为打开
                    led_on = true;
                    // 发送响应,LED已打开
                    httpd_resp_sendstr(req, "LED turned ON");
                // 如果查询参数为"off"
                } else if (strcmp(param, "off") == 0) {
                    // 设置LED引脚为低电平
                    gpio_set_level(LED_GPIO, 0);
                    // 设置LED状态为关闭
                    led_on = false;
                    // 发送响应,LED已关闭
                    httpd_resp_sendstr(req, "LED turned OFF");
                // 如果查询参数不为"on"或"off"
                } else {
                    // 发送响应,LED状态无效
                    httpd_resp_sendstr(req, "Invalid LED state");
                }
            // 如果查询参数不为"state"
            } else {
                // 发送响应,缺少"state"参数
                httpd_resp_sendstr(req, "Missing 'state' param");
            }
        // 如果URL查询字符串的长度不大于1
        } else {
            // 发送响应,未提供查询参数
            httpd_resp_sendstr(req, "No query provided");
        }

        // 返回ESP_OK
        return ESP_OK;
    }

    // 初始化 GPIO
    void led_gpio_init(void)
    {
        gpio_config_t io_conf = {
            .pin_bit_mask = 1ULL << LED_GPIO,
            .mode = GPIO_MODE_OUTPUT,
            .pull_down_en = 0,
            .pull_up_en = 0,
            .intr_type = GPIO_INTR_DISABLE
        };
        gpio_config(&io_conf);
        gpio_set_level(LED_GPIO, 0); // 默认关闭
    }

    // 启动 HTTP 服务器
    httpd_handle_t start_webserver(void)
    {
        httpd_config_t config = HTTPD_DEFAULT_CONFIG();
        httpd_handle_t server = NULL;

        if (httpd_start(&server, &config) == ESP_OK) {
            httpd_uri_t index_uri = {
                .uri       = "/",
                .method    = HTTP_GET,
                .handler   = index_handler,
                .user_ctx  = NULL
            };
            httpd_register_uri_handler(server, &index_uri);

            // 定义一个httpd_uri_t类型的变量led_uri// 设置uri为"/led"
            httpd_uri_t led_uri = {
                .uri       = "/led",
                .method    = HTTP_GET,
                .handler   = led_handler,
                .user_ctx  = NULL
            };
            httpd_register_uri_handler(server, &led_uri);
        }

        return server;
    }

    // 初始化 Wi-Fi AP 模式
    void wifi_init_softap(void)
    {
        ESP_ERROR_CHECK(esp_netif_init());
        ESP_ERROR_CHECK(esp_event_loop_create_default());
        esp_netif_create_default_wifi_ap();

        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        ESP_ERROR_CHECK(esp_wifi_init(&cfg));

        wifi_config_t wifi_config = {
            .ap = {
                .ssid = "ESP32_WEB",
                .ssid_len = strlen("ESP32_WEB"),
                .password = "12345678",
                .max_connection = 2,
                .authmode = WIFI_AUTH_WPA_WPA2_PSK
            },
        };

        if (strlen((char *)wifi_config.ap.password) == 0)
            wifi_config.ap.authmode = WIFI_AUTH_OPEN;

        ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
        ESP_ERROR_CHECK(esp_wifi_start());

        ESP_LOGI(TAG, "Wi-Fi AP started. SSID:%s password:%s", "ESP32_WEB", "12345678");
    }

    // 主函数
    void app_main(void)
    {
        ESP_ERROR_CHECK(nvs_flash_init());
        wifi_init_softap();
        led_gpio_init();
        start_webserver();
    }

index.html

复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ESP32 Web Server</title>
    <style>
      body {
        background-color: #202020;
        color: #fff;
        text-align: center;
        height: 100vh;
        margin: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 20px;
        box-sizing: border-box;
        font-family: Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
      }

      h1 {
        font-size: 5vw;
        margin-bottom: 5vh;
      }

      #led-status {
        font-size: 4vw;
        margin-bottom: 2vh;
      }

      button {
        padding: 10px 20px;
        font-size: 4vw;
        background-color: #0cc79e;
        color: #202020;
        border: none;
        border-radius: 5px;
        cursor: pointer;
      }

      button:hover {
        background-color: #0aa386;
      }

      footer {
        margin-top: auto;
        padding: 20px;
        border-top: 1px solid #444;
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        font-size: 3vw;
      }

      footer small {
        color: #aaa;
        line-height: 1.5em;
        word-break: break-word;
      }

      @media (max-width: 480px) {
        footer {
          align-items: flex-start;
        }
      }
    </style>
  </head>

  <body>
    <h1>ESP32 LED 控制</h1>

    <div id="led-status">当前状态: 关闭</div>
    <button onclick="toggleLED()">切换 LED 开关</button>

    <footer>
      天雾雨涟水
      <small>简介:ESP32静态Web项目练习</small>
      <small>本页面为静态网站,部署在ESP32S3(服务器):</small>
      <small style="color: #0cc79e"
        >ESP32s3做服务器(AP模式),手机连接esp热点,浏览器进入192.168.4.1</small
      >
      <small style="color: #595d5c">html的内容很多,之后再慢慢优化界面</small>
      <small style="color: #595d5c">将就着先用^V^</small>
      <small style="color: #595d5c">给自己引流~</small>
      <small style="color: #97a19e">个人博客链接:</small>
      <small style="color: #25c099"
        >https://www.cnblogs.com/tianwuyvlianshui/</small
      >
      <small style="color: #97a19e">Gitee链接:</small>
      <small style="color: #25c099">https://gitee.com/sword-level_0/</small>
      <small style="color: #97a19e">Github链接:</small>
      <small style="color: #25c099">https://github.com/jianzhiji/</small>
    </footer>

    <script>
      let ledOn = false;

      function toggleLED() {
        ledOn = !ledOn;
        document.getElementById("led-status").innerText =
          "当前状态: " + (ledOn ? "开启" : "关闭");

        // 向ESP32发送GET请求(根据你固件的URL接口设计修改)
        fetch("/led?state=" + (ledOn ? "on" : "off"))
          .then((response) => {
            if (!response.ok) {
              throw new Error("网络错误");
            }
            return response.text();
          })
          .then((data) => {
            console.log("ESP响应:", data);
          })
          .catch((error) => {
            alert("请求失败: " + error);
          });
      }
    </script>
  </body>
</html>

CMakeLists.txt

c 复制代码
idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS ".")
                    
# 嵌入 HTML 和 PNG 到可执行文件(将源文件转化成bin,二进制文件)
target_add_binary_data(${COMPONENT_TARGET} "index.html" TEXT)