自学内容网 自学内容网

ESP32运行轻量级 Web 服务器入门(基于ESP-IDF)

主要参考资料:
乐鑫官方ESP-IDF文档 HTTP服务器: https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32/api-reference/protocols/esp_http_server.html#
NVS入门(基于ESP-IDF): https://blog.csdn.net/qq_40773212/article/details/135595361

简介

HTTP Server 组件提供了在 ESP32 上运行轻量级 Web 服务器的功能。

HTTP Server参考代码

/* URI 处理函数,在客户端发起 GET /uri 请求时被调用 */
esp_err_t get_handler(httpd_req_t *req)
{
    /* 发送回简单的响应数据包 */
    const char resp[] = "URI GET Response";
    httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

/* URI 处理函数,在客户端发起 POST/uri 请求时被调用 */
esp_err_t post_handler(httpd_req_t *req)
{
    /* 定义 HTTP POST 请求数据的目标缓存区
     * httpd_req_recv() 只接收 char* 数据,但也可以是
     * 任意二进制数据(需要类型转换)
     * 对于字符串数据,null 终止符会被省略,
     * content_len 会给出字符串的长度 */
    char content[100];

    /* 如果内容长度大于缓冲区则截断 */
    size_t recv_size = MIN(req->content_len, sizeof(content));

    int ret = httpd_req_recv(req, content, recv_size);
    if (ret <= 0) {  /* 返回 0 表示连接已关闭 */
        /* 检查是否超时 */
        if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
            /* 如果是超时,可以调用 httpd_req_recv() 重试
             * 简单起见,这里我们直接
             * 响应 HTTP 408(请求超时)错误给客户端 */
            httpd_resp_send_408(req);
        }
        /* 如果发生了错误,返回 ESP_FAIL 可以确保
         * 底层套接字被关闭 */
        return ESP_FAIL;
    }

    /* 发送简单的响应数据包 */
    const char resp[] = "URI POST Response";
    httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
    return ESP_OK;
}

/* GET /uri 的 URI 处理结构 */
httpd_uri_t uri_get = {
    .uri      = "/uri",
    .method   = HTTP_GET,
    .handler  = get_handler,
    .user_ctx = NULL
};

/* POST/uri 的 URI 处理结构 */
httpd_uri_t uri_post = {
    .uri      = "/uri",
    .method   = HTTP_POST,
    .handler  = post_handler,
    .user_ctx = NULL
};

/* 启动 Web 服务器的函数 */
httpd_handle_t start_webserver(void)
{
    /* 生成默认的配置参数 */
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();

    /* 置空 esp_http_server 的实例句柄 */
    httpd_handle_t server = NULL;

    /* 启动 httpd server */
    if (httpd_start(&server, &config) == ESP_OK) {
        /* 注册 URI 处理程序 */
        httpd_register_uri_handler(server, &uri_get);
        httpd_register_uri_handler(server, &uri_post);
    }
    /* 如果服务器启动失败,返回的句柄是 NULL */
    return server;
}

/* 停止 Web 服务器的函数 */
void stop_webserver(httpd_handle_t server)
{
    if (server) {
        /* 停止 httpd server */
        httpd_stop(server);
    }
}

用户在Web网页上输入Wi-Fi账号密码

NVS检查账号密码

  //ui显示操作界面
  ui_init();
  //查看NVS有无存储账号
  nvs_handle_t my_nvs_handle;
  err = nvs_open("WI-FI", NVS_READWRITE, &my_nvs_handle);
  if (err != ESP_OK) {
    printf("Handle error\n");
    // Handle error
  } else {
    // Check if key exists
    err = nvs_get_str(my_nvs_handle, "SSID", SSID_value, &SSID_size);
    err = nvs_get_str(my_nvs_handle, "PWD", PWD_value, &PWD_size);
    if (err == ESP_OK) {
        //有账号密码,就直接连接并且显示连接界面
        ESP_LOGI(TAG, "Value of 'SSID' is: %s", SSID_value);
        ESP_LOGI(TAG, "Value of 'PWD' is: %s", PWD_value);
        lv_disp_load_scr(ui_WiFiconnect);
    } else if (err == ESP_ERR_NVS_NOT_FOUND) {
        // The key does not exist
        ESP_LOGI(TAG, "Key does not exist in NVS.");
        lv_disp_load_scr(ui_WiFireset);
        webConfigWifiLoop();
    } 
        // Close the NVS handle when done
        nvs_close(my_nvs_handle);
  }

运行HTTP Server

void webConfigWifiLoop(void) 
{
    webConfigWifiInit();

    /* Allocate memory for server data */
    server_data = calloc(1, sizeof(struct file_server_data));
    if (!server_data) {
        printf("\r\nFailed to allocate memory for server data\r\n");
        return;
    }

    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    config.max_open_sockets = 1;
    config.backlog_conn = 1;
    config.lru_purge_enable = true;
    config.max_uri_handlers = 15;
    config.stack_size = 8192; 
    // config.server_port = 89;
    config.uri_match_fn = httpd_uri_match_wildcard;
    
    printf("Starting Web server on port: %d\r\n", config.server_port);

    if (httpd_start(&webConfigWifi_httpd, &config) == ESP_OK)
    {
        static httpd_uri_t root = {
            .uri = "/",
            .method = HTTP_GET,
            .handler = main_page_handler,
            .user_ctx  = NULL
        };
        root.user_ctx = server_data;
        httpd_register_uri_handler(webConfigWifi_httpd, &root);

        static httpd_uri_t wifi_data = {
            .uri       = "/wifi_data",
            .method    = HTTP_POST,
            .handler   = wifi_config_handler,
            .user_ctx  = NULL
        };
        wifi_data.user_ctx = server_data;
        httpd_register_uri_handler(webConfigWifi_httpd, &wifi_data);

        httpd_register_err_handler(webConfigWifi_httpd, HTTPD_404_NOT_FOUND, http_404_error_handler);
    }

    start_dns_server();

    while(1) {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

运行DNS服务器

在这部分,DNS服务器被设置为监听DNS查询,并针对所有类型A(A记录,用于IPv4地址解析)的查询回复软AP(Soft Access Point)的IP地址。

/*
    Sets up a socket and listen for DNS queries,
    replies to all type A queries with the IP of the softAP
*/
void dns_server_task(void *pvParameters)
{
    char rx_buffer[128];
    char addr_str[128];
    int addr_family;
    int ip_protocol;

    while (1) {

        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(DNS_PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;
        inet_ntoa_r(dest_addr.sin_addr, addr_str, sizeof(addr_str) - 1);

        int sock = socket(addr_family, SOCK_DGRAM, ip_protocol);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket created");

        int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
        if (err < 0) {
            ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
        }
        ESP_LOGI(TAG, "Socket bound, port %d", DNS_PORT);

        while (1) {
            ESP_LOGI(TAG, "Waiting for data");
            struct sockaddr_in6 source_addr; // Large enough for both IPv4 or IPv6
            socklen_t socklen = sizeof(source_addr);
            int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);

            // Error occurred during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
                close(sock);
                break;
            }
            // Data received
            else {
                // Get the sender's ip address as string
                if (source_addr.sin6_family == PF_INET) {
                    inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);
                } else if (source_addr.sin6_family == PF_INET6) {
                    inet6_ntoa_r(source_addr.sin6_addr, addr_str, sizeof(addr_str) - 1);
                }

                // Null-terminate whatever we received and treat like a string...
                rx_buffer[len] = 0;

                char reply[DNS_MAX_LEN];
                int reply_len = parse_dns_request(rx_buffer, len, reply, DNS_MAX_LEN);

                ESP_LOGI(TAG, "Received %d bytes from %s | DNS reply with len: %d", len, addr_str, reply_len);
                if (reply_len <= 0) {
                    ESP_LOGE(TAG, "Failed to prepare a DNS reply");
                } else {
                    int err = sendto(sock, reply, reply_len, 0, (struct sockaddr *)&source_addr, sizeof(source_addr));
                    if (err < 0) {
                        ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                        break;
                    }
                }
            }
        }

        if (sock != -1) {
            ESP_LOGE(TAG, "Shutting down socket");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}

void start_dns_server(void)
{
    xTaskCreate(dns_server_task, "dns_server", 4096, NULL, 5, NULL);
}

原文地址:https://blog.csdn.net/qq_40773212/article/details/142362863

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!