ESP32S3-WIFI

目录

一 、WIFI

1、AP热点模式

1.1 AP热点模式

1.2 AP热点的层次构成

1.3 标准的ESP32热点模式流程

1.4 原生命令行完整编译下载过程

2、Station设备模式

 2.1 STA模式的层次架构

 2.1 连接手机热点

二、TCP

1、基于LwIPSocket的TCP客户端 

2、基于LwIPSocket的TCP服务端

三、UDP 

1、UDP与TCP

1.1 相同点 

1.2  不同点

2、UDP客户端

3、UDP服务端

四、HTTP协议

1、协议作用

2、协议报文格式 

3、HTTP URL超级链接

五、HTTP_Get方法

1、HTTP的回调函数

2、Get函数 

3、实验 

六、 HTTP_Post方法

1、get_HTML

2、HTML_POST数据

 七、 HTTP_PUT方法

八、HTTP_Request获取天气信息

九、 WebSocket协议

1、WebSocket数据交换模型

2 、WebSocket数据帧格式

十、WebSocket脚本

1、浏览器编写脚本读取服务器数据

1.1 服务端

1.2 浏览器端

1.3 ESP32作为客户端访问服务器

十 一、 MQTT 

1、 MQTT简介

2、 MQTT协议

3、 MQTT客户端

十二、 ESP-Now

1、 ESP-Now是什么

2、 ESP-Now数据帧格式

 3、 获取MAC地址

4、发送数据

5、接收数据

6、 广播数据


一 、WIFI

1、AP热点模式

1.1 AP热点模式

1.2 AP热点的层次构成

1.3 标准的ESP32热点模式流程

  • 例程文件

        Espressif\frameworks\esp-idf-v5.0\examples\wifi\getting_started\softAP

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sys.h"

/* The examples use WiFi configuration that you can set via project configuration menu.

   If you'd rather not, just change the below entries to strings with
   the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID      CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS      CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_WIFI_CHANNEL   CONFIG_ESP_WIFI_CHANNEL
#define EXAMPLE_MAX_STA_CONN       CONFIG_ESP_MAX_STA_CONN

static const char *TAG = "wifi softAP";

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                    int32_t event_id, void* event_data)
{
    if (event_id == WIFI_EVENT_AP_STACONNECTED) {               //连接事件
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
                 MAC2STR(event->mac), event->aid);
    } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {     //断开连接事件
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
                 MAC2STR(event->mac), event->aid);
    }
}

void wifi_init_softap(void)
{
    ESP_ERROR_CHECK(esp_netif_init());                  //启动LWIP协议栈 task
    ESP_ERROR_CHECK(esp_event_loop_create_default());   //创建Event task
    esp_netif_create_default_wifi_ap();                 

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));               //初始化WIFI驱动 

    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &wifi_event_handler,        //注册需要处理事件回调
                                                        NULL,
                                                        NULL));

    wifi_config_t wifi_config = {                       //配置WIFI信息
        .ap = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,              //WIFI名
            .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
            .channel = EXAMPLE_ESP_WIFI_CHANNEL,
            .password = EXAMPLE_ESP_WIFI_PASS,          //WIFI密码
            .max_connection = EXAMPLE_MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK,         //授权模式
            .pmf_cfg = {
                    .required = false,
            },
        },
    };
    if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));       //配置Dirver
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());                      //启动Dirver

    ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
             EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
}

void app_main(void)
{
    //Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();       //初始化存储空间
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");
    wifi_init_softap();         //软件热点
}
  • 编译并下载

1.4 原生命令行完整编译下载过程

  • 切换到工程目录后设置target

        idf.py set-target esp32s3

  • 通过menuconfig修改热点名字以及其他参数

        idf.py menuconfig

  • 编译所有的代码、或者仅编译应用程序代码

        idf.py all 

        idf.py app

  • 下载代码到flash

        idf.py flash        

  • 打开串口监控

        idf.py monitor

2、Station设备模式

 2.1 STA模式的层次架构

基本与AP模式一致、添加了事件组来等待某些事件以及不同的事件处理;

 2.1 连接手机热点

  • 例程文件

        Espressif\frameworks\esp-idf-v5.0\examples\wifi\getting_started\station

  • menuconfug 配置连接的热点

二、TCP

1、基于LwIPSocket的TCP客户端 

  • 客户端与服务端的通信流程

  • 对应代码的实现过程
/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
#include "addr_from_stdin.h"
#include "lwip/err.h"
#include "lwip/sockets.h"

#if defined(CONFIG_EXAMPLE_IPV4)
#define HOST_IP_ADDR "10.77.93.176" // 手动配置IP也可以用menuconfig配置
#elif defined(CONFIG_EXAMPLE_IPV6)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR
#else
#define HOST_IP_ADDR ""
#endif

#define PORT 3333 // 手动配置端口号也可以用menuconfig配置

static const char *TAG = "example";
static const char *payload = "Hello this's Message from little";
int err;

static void tcp_client_task(void *pvParameters)
{
    char rx_buffer[128];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;
    bool bShutdown = false;

    while (1)
    {
#if defined(CONFIG_EXAMPLE_IPV4)
        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = inet_addr(host_ip);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;
#elif defined(CONFIG_EXAMPLE_IPV6)
        struct sockaddr_in6 dest_addr = {0};
        inet6_aton(host_ip, &dest_addr.sin6_addr);
        dest_addr.sin6_family = AF_INET6;
        dest_addr.sin6_port = htons(PORT);
        dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
        addr_family = AF_INET6;
        ip_protocol = IPPROTO_IPV6;
#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN)
        struct sockaddr_storage dest_addr = {0};
        ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_STREAM, &ip_protocol, &addr_family, &dest_addr));
#endif
        int sock = socket(addr_family, SOCK_STREAM, ip_protocol); // 初始化Socket
        if (sock < 0)
        {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT);

        err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6)); // 建立连接
        if (err != 0)
        {
            ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Successfully connected");

        err = send(sock, payload, strlen(payload), 0); // 向服务器发送一段数据
        if (err < 0)
        {
            ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
            break;
        }

        while (1)
        {

            int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); // 接收服务端发来的数据
            // Error occurred during receiving
            if (len < 0)
            {
                ESP_LOGE(TAG, "recv failed: errno %d", errno);
                break;
            }
            // Data received
            else
            {
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string 这个是添加截止符号防止接收乱码
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);

                if (rx_buffer[0] == 's')
                {
                    bShutdown = true;
                    break; // 首字母为S跳出当前while,进行关闭TCP客户端操作
                }
                else
                {
                    err = send(sock, rx_buffer, strlen(rx_buffer), 0); // 非S开头把客户端接收到的数据发送回给服务器
                    if (err < 0)
                    {
                        ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                        break;
                    }
                }
            }

            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }

        if (sock != -1)
        {
            ESP_LOGE(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
        if (bShutdown == true)
        {
            break; // 如果bShutdown为true跳出最外层while 然后删除该任务即关闭TCP客户端
        }
    }
    vTaskDelete(NULL);
}

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());

    xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
}
  • 修改IP和端口号

  • 配置要连接WIF的I账号密码

  • 通过手机配置生成要连接WIFI然后开发板连接上这个WIFI,用手机上的“有人网络助手”创建一个tcp Server,根据生成的IP号和端口号配置到代码中

  • 连接上后开发板作为客户端首先发送预先准备好的数据给手机服务端

  • 服务端再发送数据给客户端

  • 客户端再收到后又原原本本的发回给服务端,服务端就会收到发送的内容

  • 服务段再发送s结束TCP连接

2、基于LwIPSocket的TCP服务端

/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>


#define PORT                        CONFIG_EXAMPLE_PORT
#define KEEPALIVE_IDLE              CONFIG_EXAMPLE_KEEPALIVE_IDLE
#define KEEPALIVE_INTERVAL          CONFIG_EXAMPLE_KEEPALIVE_INTERVAL
#define KEEPALIVE_COUNT             CONFIG_EXAMPLE_KEEPALIVE_COUNT

static const char *TAG = "example";

static void do_retransmit(const int sock)
{
    int len;
    char rx_buffer[128];

    do {
        len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);      //接收客户端数据
        if (len < 0) {
            ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
        } else if (len == 0) {
            ESP_LOGW(TAG, "Connection closed");
        } else {
            rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string
            ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);

            // send() can return less bytes than supplied length.
            // Walk-around for robust implementation.
            int to_write = len;
            while (to_write > 0) {
                int written = send(sock, rx_buffer + (len - to_write), to_write, 0);        //发送接收回的数据
                if (written < 0) {
                    ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                }
                to_write -= written;
            }
        }
    } while (len > 0);      //成功接收并发送后再次循环等待接收数据
}

static void tcp_server_task(void *pvParameters)
{
    char addr_str[128];
    int addr_family = (int)pvParameters;
    int ip_protocol = 0;
    int keepAlive = 1;
    int keepIdle = KEEPALIVE_IDLE;
    int keepInterval = KEEPALIVE_INTERVAL;
    int keepCount = KEEPALIVE_COUNT;
    struct sockaddr_storage dest_addr;

    if (addr_family == AF_INET) {
        struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
        dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
        dest_addr_ip4->sin_family = AF_INET;
        dest_addr_ip4->sin_port = htons(PORT);
        ip_protocol = IPPROTO_IP;
    }
#ifdef CONFIG_EXAMPLE_IPV6
    else if (addr_family == AF_INET6) {
        struct sockaddr_in6 *dest_addr_ip6 = (struct sockaddr_in6 *)&dest_addr;
        bzero(&dest_addr_ip6->sin6_addr.un, sizeof(dest_addr_ip6->sin6_addr.un));
        dest_addr_ip6->sin6_family = AF_INET6;
        dest_addr_ip6->sin6_port = htons(PORT);
        ip_protocol = IPPROTO_IPV6;
    }
#endif

    int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);        //socket
    if (listen_sock < 0) {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        vTaskDelete(NULL);
        return;
    }
    int opt = 1;
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)
    // Note that by default IPV6 binds to both protocols, it is must be disabled
    // if both protocols used at the same time (used in CI)
    setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
#endif

    ESP_LOGI(TAG, "Socket created");

    int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));      //bind绑定侦听sock到一个端口
    if (err != 0) {
        ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
        ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
        goto CLEAN_UP;
    }
    ESP_LOGI(TAG, "Socket bound, port %d", PORT);

    err = listen(listen_sock, 1);                                           //liste侦听绑定的端口
    if (err != 0) {
        ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
        goto CLEAN_UP;
    }

    while (1) {

        ESP_LOGI(TAG, "Socket listening");

        struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
        socklen_t addr_len = sizeof(source_addr);
        int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);     //accept 接收当前的连接
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
            break;
        }

        // Set tcp keepalive option
        setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
        // Convert ip address to string
        if (source_addr.ss_family == PF_INET) {
            inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
        }
#ifdef CONFIG_EXAMPLE_IPV6
        else if (source_addr.ss_family == PF_INET6) {
            inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1);
        }
#endif
        ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);

        do_retransmit(sock);                    //recv  send 

        shutdown(sock, 0);
        close(sock);                            //close 
    }

CLEAN_UP:
    close(listen_sock);
    vTaskDelete(NULL);
}

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());

#ifdef CONFIG_EXAMPLE_IPV4
    xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET, 5, NULL);
#endif
#ifdef CONFIG_EXAMPLE_IPV6
    xTaskCreate(tcp_server_task, "tcp_server", 4096, (void*)AF_INET6, 5, NULL);
#endif
}
  • 配置端口号和IPV的类型

  • 配置要连接的WIFI

  • 手机生成一个热点作为路由器,然后用“有人网络助手”模拟一个客户端来
  • 服务端连接后生成的IP地址和端口号

  • 连接成功后客户端向开发板服务端发送数据

  • 服务端接收到数据后打印并再发回客户端

三、UDP 

1、UDP与TCP

1.1 相同点 

  • 两者都是数据传输协议

1.2  不同点

  • 编程模型上

  • 没有建立侦听连接的过程
  • UDP的recyfrom()会阻塞当前进程等待客户端发送数据,recyfrom()接收的数据会包含客户端的IP地址和端口;而TCP的accept()不会包含;
  • UDP的sendto()会包含服务端的IP和端口,TCP的则不包含,因为TCP已经建立了连接不需要再包含;
  • 应用方面
  • TCP是基于连接UDP是没有任何连接的数据传输
  • TCP的数据传输是可靠的,UDP是不可靠的发送方不能够保证接收方能够接收到数据
  • TCP传输速度慢于UDP
  • TCP只能做点对点的数据传输,UDP可以做广播和组播数据传输。

2、UDP客户端

/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include "addr_from_stdin.h"

#if defined(CONFIG_EXAMPLE_IPV4)
#define HOST_IP_ADDR "10.48.33.164"
#elif defined(CONFIG_EXAMPLE_IPV6)
#define HOST_IP_ADDR CONFIG_EXAMPLE_IPV6_ADDR
#else
#define HOST_IP_ADDR ""
#endif

#define PORT 5001

static const char *TAG = "example";
static const char *payload = "Message from UDP Clinet";


static void udp_client_task(void *pvParameters)
{
    char rx_buffer[128];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;

    while (1) {

#if defined(CONFIG_EXAMPLE_IPV4)
        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = inet_addr(HOST_IP_ADDR);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;
#elif defined(CONFIG_EXAMPLE_IPV6)
        struct sockaddr_in6 dest_addr = { 0 };
        inet6_aton(HOST_IP_ADDR, &dest_addr.sin6_addr);
        dest_addr.sin6_family = AF_INET6;
        dest_addr.sin6_port = htons(PORT);
        dest_addr.sin6_scope_id = esp_netif_get_netif_impl_index(EXAMPLE_INTERFACE);
        addr_family = AF_INET6;
        ip_protocol = IPPROTO_IPV6;
#elif defined(CONFIG_EXAMPLE_SOCKET_IP_INPUT_STDIN)
        struct sockaddr_storage dest_addr = { 0 };
        ESP_ERROR_CHECK(get_addr_from_stdin(PORT, SOCK_DGRAM, &ip_protocol, &addr_family, &dest_addr));
#endif

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

        // Set timeout
        struct timeval timeout;
        timeout.tv_sec = 10;
        timeout.tv_usec = 0;
        setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);
        ESP_LOGI(TAG, "Socket created, sending to %s:%d", HOST_IP_ADDR, PORT);
        //begin send init message
        int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); // sendto
        if (err < 0)
        {
            ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Message sent");
        // finish sending;

        while (1) {

            struct sockaddr_storage 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);// recfrom 接收服务器发来的数据

            // Error occurred during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recvfrom timeou");
                // break;
            }
            // Data received
            else {
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
                if (strncmp(rx_buffer, "reset: ", 5) == 0) {
                    ESP_LOGI(TAG, "Received expected message, re-init"); // 收到reset重新初始化UDP客户端
                    break;
                }
                else
                {
                    ESP_LOGI(TAG, "Message return");
                    err = sendto(sock, rx_buffer, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); // sendto
                    if (err < 0)
                    {
                        ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                        break;
                    }
                }
            }

            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }

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

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());

    xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL);
}
  • 开机开发板作为服务端发送数据给客户端

  • 超过20s没有收到信息对应等待超时提示

  • 服务端发送给开发板客户端数据后,客户端再把数据回给服务端

  • 服务端发送reset给服务端后就重启客户端的UDP重新连接服务端

3、UDP服务端

/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

#define PORT CONFIG_EXAMPLE_PORT

static const char *TAG = "example";

static void udp_server_task(void *pvParameters)
{
    char rx_buffer[128];
    char addr_str[128];
    int addr_family = (int)pvParameters;
    int ip_protocol = 0;
    struct sockaddr_in6 dest_addr;

    while (1) {

        if (addr_family == AF_INET) {
            struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
            dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
            dest_addr_ip4->sin_family = AF_INET;
            dest_addr_ip4->sin_port = htons(PORT);
            ip_protocol = IPPROTO_IP;
        } else if (addr_family == AF_INET6) {
            bzero(&dest_addr.sin6_addr.un, sizeof(dest_addr.sin6_addr.un));
            dest_addr.sin6_family = AF_INET6;
            dest_addr.sin6_port = htons(PORT);
            ip_protocol = IPPROTO_IPV6;
        }

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

#if defined(CONFIG_LWIP_NETBUF_RECVINFO) && !defined(CONFIG_EXAMPLE_IPV6)
        int enable = 1;
        lwip_setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable));
#endif

#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)
        if (addr_family == AF_INET6) {
            // Note that by default IPV6 binds to both protocols, it is must be disabled
            // if both protocols used at the same time (used in CI)
            int opt = 1;
            setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
            setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
        }
#endif
        // Set timeout
        struct timeval timeout;
        timeout.tv_sec = 10;
        timeout.tv_usec = 0;
        setsockopt (sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);

        int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));     //绑定IP地址和端口
        if (err < 0) {
            ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
        }
        ESP_LOGI(TAG, "Socket bound, port %d", PORT);

        struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
        socklen_t socklen = sizeof(source_addr);

#if defined(CONFIG_LWIP_NETBUF_RECVINFO) && !defined(CONFIG_EXAMPLE_IPV6)
        struct iovec iov;
        struct msghdr msg;
        struct cmsghdr *cmsgtmp;
        u8_t cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))];

        iov.iov_base = rx_buffer;
        iov.iov_len = sizeof(rx_buffer);
        msg.msg_control = cmsg_buf;
        msg.msg_controllen = sizeof(cmsg_buf);
        msg.msg_flags = 0;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_name = (struct sockaddr *)&source_addr;
        msg.msg_namelen = socklen;
#endif

        while (1) {
            ESP_LOGI(TAG, "Waiting for data");
#if defined(CONFIG_LWIP_NETBUF_RECVINFO) && !defined(CONFIG_EXAMPLE_IPV6)
            int len = recvmsg(sock, &msg, 0);
#else
            int len = recvfrom(sock, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);   //阻塞当前进程等待客户端发送数据,source_addr为客户端的IP地址和端口参赛
#endif
            // Error occurred during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recvfrom failed: errno %d", errno);
                break;
            }
            // Data received
            else {
                // Get the sender's ip address as string
                if (source_addr.ss_family == PF_INET) {
                    inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
#if defined(CONFIG_LWIP_NETBUF_RECVINFO) && !defined(CONFIG_EXAMPLE_IPV6)
                    for ( cmsgtmp = CMSG_FIRSTHDR(&msg); cmsgtmp != NULL; cmsgtmp = CMSG_NXTHDR(&msg, cmsgtmp) ) {
                        if ( cmsgtmp->cmsg_level == IPPROTO_IP && cmsgtmp->cmsg_type == IP_PKTINFO ) {
                            struct in_pktinfo *pktinfo;
                            pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsgtmp);
                            ESP_LOGI(TAG, "dest ip: %s\n", inet_ntoa(pktinfo->ipi_addr));
                        }
                    }
#endif
                } else if (source_addr.ss_family == PF_INET6) {
                    inet6_ntoa_r(((struct sockaddr_in6 *)&source_addr)->sin6_addr, addr_str, sizeof(addr_str) - 1);
                }
                u16_t iPort = ntohs(((struct sockaddr_in6 *)&source_addr)->sin6_port);
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string...
                ESP_LOGI(TAG, "Received %d bytes from IP:%s: Port:%d", len, addr_str,iPort);
                ESP_LOGI(TAG, "%s", rx_buffer);

                int err = sendto(sock, rx_buffer, 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 and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());

#ifdef CONFIG_EXAMPLE_IPV4
    xTaskCreate(udp_server_task, "udp_server", 4096, (void*)AF_INET, 5, NULL);
#endif
#ifdef CONFIG_EXAMPLE_IPV6
    xTaskCreate(udp_server_task, "udp_server", 4096, (void*)AF_INET6, 5, NULL);
#endif

}
  • 开发板为UDP服务端,连接并绑定WIFI端口,分配客户端要连接的IP地址以及端口

  • 手机上连接配置服务器分配的端口号连接上服务端

  • 手机上的软件作为客户端发送数据给服务器,服务器显示接收到的数据以及客户端的IP地址和端口号

  • 服务器发回客户端发来的数据

四、HTTP协议

1、协议作用

  • 在设备上建立HTTP网页服务器,在PC端或者手机端就可以使用浏览器获取设备是的数据并控制设备

2、协议报文格式 

  • HTTP是基于客户端和服务端的协议
    • 客户端发送HTTP Request 请求数据包给服务端,服务端根据特定格式分析请求数据包的数据;服务端发回HTTP Response 应答数据包;
  • HTTP Request 请求数据包格式
    • Request Line:请求数据行;格式分析如下
      • method:请求的方法,比如说get方法
      • sp:空行(是一个空格)
      • URL:请求的超级链接地址,是一个相对的地址
      • sp:空行
      • version;HTTP的版本
      • cr:回车
      • lf:换行
    • Header:报文头部;报文的头部数据是成对出现的(即一个header field name和一个value 中间用分号:分割开每一对属性间用回一次换行分割开);它的主要作用是告诉服务器本机的浏览器有什么样的特性特点,服务器根据所有的属性值返回对应的数据。格式分析如下;
      • header field name:本机的属性的名字
      • value:属性的值
    • A Blank Line:空行
    • Boy:报文主体,是一个可选项可以省略

  • HTTP Response 应答数据包格式
    • Status Line:状态行;表明服务器处理完请求的一个状态
      • version:HTTP的版本
      • sp:空格
      • status code:状态代码
      • sp:空格
      • phrase:状态短语的描述
      • cr:回车
      • lf:换行
    • Header:返回的数据包头部信息;描述服务器端的关于HTTP的属性
      • header field name:本机的属性的名字
      • value:属性的值
    • A Blank Line:空行
    • Boy:报文主体,是一个可选项,这个地方可以返回所有的HTTP的网页内容就是构成网页的全部内容;

  • method:方法用来请求服务器做什么
    • get:取得网页内容;
    • post:把相应的数据提交到服务器,修改服务器对应的内容;
    • ...
  • status code:状态代码
    • 200:处理成功
    • 301:重定向
    • 404:没有找到对应的资源对应的网页

  • 请求包

  • 应答包

       

  • 应答包的主体:里面的内容就是构成网页的所有CSS      

3、HTTP URL超级链接

  • 服务器会根据超级链接的内容和格式返回相应的内容
  • URL超级链接的格式
    • (http)协议部分(也还有其他协议如https加密协议、ftp文件传输协议)
    • (user:pass)用户信息部分,包括用户名用户密码
    • (www.micro-studiuo.com)域名代表了服务器的IP在这个IP底下有对应的目录
    • (80)端口,对于http来说它的默认端口为80端口默认端口可以不写,指定端口要写,https默认端口443,ftp默认21控制端口20数据端口
    • (product/index.html)表示网页资源在服务器上所存储的目录
    • (?id=1&number=5)网页的后面用一个问号来携带所需要查询的参这里有两个参数第一个是id=1第二个参数为number=5;网页服务器会根据这个参数返回不同内容的网页
    • (#top)锚点,也叫片段,top表示获得网页后定位到顶部位置

五、HTTP_Get方法

1、HTTP的回调函数

例程代码首连上路由器WIFI,然后start_webserver函数中注册三个回调函数,每一个对应一个资源网页

  •  httpd_register_uri_handler(server, &hello);

    • 对应的是GET方法

  • httpd_register_uri_handler(server, &echo);

    • 对应的是POST方法

  • httpd_register_uri_handler(server, &ctrl);
    • 对应的是PUT方法

2、Get函数 

主要作用的:首先是对请求报文格式的解析,其次是服务器组织应答报文返回网页数据;浏览器从服务器上取得数据;

static const httpd_uri_t hello = {
    .uri       = "/hello",
    .method    = HTTP_GET,
    .handler   = hello_get_handler,
    /* Let's pass response string in user
     * context to demonstrate it's usage */
    .user_ctx  = "Hello World!"
};
  • hello_get_handler:处理函数的作用;
    • 解析HTTP Request中的Header:即解析浏览器发过来的一些信息
      • 获取一些服务器感兴趣的键值对;Host对应的键值对就是告诉服务器浏览器要访问的网址;
      • Accept-Language是获取浏览器能够接收的语言;

      • Connection 获取浏览器的Connection属性

    • 解析超级链接中查询的字符串
      • 查询浏览器发过来的查询数据返回不同的网页数据
      • 浏览器查询id,服务器在req->user_ctx返回数据
      • 浏览器查询number,服务器在req->user_ctx返回数据
    • 组织服务器应答的头部信息返回给浏览器
      • 设置应答数据包的两个头部信息两个个键值对,在调用httpd_resp_send发回去
            /* Set some custom headers */
            httpd_resp_set_hdr(req, "server name", "Little");
            httpd_resp_set_hdr(req, "server size", "10G");

3、实验 

  • 配置要连的路由器WIFI,电脑也要连接上这个路由器

  • 在浏览器输入要访问的页面也就是192.168.0.192这个IP地址以及要访问的hello界面,浏览器就会返回这个hello界面原本设置的内容

  • ESP32S3也会解析浏览器发来的请求里面包含本机浏览器的一些属性并打印出上面我们感兴趣的对应的键值对;(浏览器要访问的页面是192.168.0.192,浏览器支持的语言是;浏览器的连接状态是一直保持连接)

  • 对服务器的查询输入查询字符串

  • 浏览器查看服务器发回的响应信息

六、 HTTP_Post方法

  • 浏览器网页提交数据给服务器;服务器存储处理数据并且返回相应的结果;
  • 浏览器网页使用表单的方式向服务器POST提交表单的数据;

1、get_HTML

  • 首先用get方法获取hello资源中,输入参数名为post查询的字符串参数,他会返回编写好的字符串型的formHtml,HTML资源;

//定义一个含有表单的字符串变量 R"() 表示控号里面的内容字符串是原生字符串不需要任何转义符号
//一个简单的HTML表单内容
//html文件类型说明
//html的开头
//html主体
//html的结尾

//html主体
    //表单头
    //表单内容
        //资源路径、对于表单用到的方法
        //表单内容:name、age、提交按键
    //表单尾
char formHtml[] = R"(
    <!DOCTYPE html>
    <html>
        <body>
            <form action="http://192.168.0.192/echo" method="POST">
                name:<input type="text" name="username">
                age:<input type="text" name="age">
                <input type="submit" value="submit">
            </form>
        </body>
    </html>     
)";

2、HTML_POST数据

  • 该HTML会对这个192.168.0.192/echo资源进行POST提交;把表单里面的内容提交给服务器;

  • echo资源收到POST提交后调用回调函数echo_post_handler中httpd_req_recv取得请求报文数据(上面表单里面的内容)再把同样的数据httpd_resp_send_chunk发回浏览器

浏览器向服务器POST的数据

服务器又返回给浏览器的数据

  • HTML使用的是浏览器表单形式发出的POST命令,get回来的请求报文的头部会比较大需要设置大一些网页服务器内存menory

 七、 HTTP_PUT方法

  • PUT方法用来修改数据;
  • 例程中在取得数据0后会注销get方法和post方法的处理函数,并且注册一个404的错误处理函数;

  • 非零的话重新注册get方法和post方法取消04的错误处理函数;

  • put的回调函数ctrl_put_handler在这里相当于一个控制型命令;

  • 浏览器的控制台发送控制命令;定义一个xhr变量产生一个XMLHttpRequest对象;调用对象的open方法输入方法"PUT",网页地址"http://192.168.0.192/ctrl",同步true;用send方法发送数值0;

八、HTTP_Request获取天气信息

  • ESP32当做浏览器发送HTTP_Request请求获取网页数据
  • http_get_task任务函数:根据网站域名getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);取得IP地址信息;创建socket,connect连接网站,write将HTTP_Reques写入网站,setsockopt设置接收超时,read读取服务器返回的信息;

  • 其本质为socket的连接数据的读写,发送数据和读取数据:

  • 发送数据write(s, REQUEST, strlen(REQUEST));这个数据就是一个HTTP Request

    static const char *REQUEST = "GET " WEB_PATH " HTTP/1.0\r\n"
        "Host: "WEB_SERVER":"WEB_PORT"\r\n"
        "User-Agent: esp-idf/1.0 esp32\r\n"
        "\r\n";

  • 读取数据;在发送完数据后服务器就会返回HTTP Response返回数据在Entiy Body中携带网页数据

  • 访问免费的天气接口网站开源免费天气预报接口API(国家气象局提供)-CSDN博客

  • 下载代码连接WIFI;发送完Request收到的完整的HTTP Response

九、 WebSocket协议

  • HTTP协议是基于请求Request和应答Response的一个模式;
  • WebSocket主要解决需要服务器返回大量资源情景;如果用HTTP需要大量重复Request和Response造成资源浪费;

1、WebSocket数据交换模型

  •  第一次先发送Request请求数据包,数据包中携带要求服务器升级到WebSocket数据 ;
    • WebSocket使用的都是GET方法
    • 发送Connection键值对为Upgrade升级
    • 主机
    • Sec-WebSocket-Key:xh... (是指xh...这些不是指Sec-WebSocket-Key:xh... 一整个)为WebSocket的键值字符串;客户端把这个键值发给服务端,服务端经过运算得到Sec-WebSocket-Accept:47..;再把这个字符串(47..)返回给客户端,客户端再把该字符串(47..)进行运算后再与原本的xh...字符串进行比较;如果相同则是正确的连接;
    • WebSocket的版本
    • Upgrade键值对为WebSocket协议
  • 服务器返回Response应答数据包,状态代码为101 ;告知客户端服务器切换协议升级到WebSocket;
    • Status Line 应答数据包的状态行
    • 升级到WebSocket协议
    • 服务器支持WebSocket版本
    • 连接为升级
    • 服务器的名字
    • 接收到Sec-WebSocket-Key运算后得到的Accept;用于客户端校验;
  • 此后客户端用WebSocket的数据格式发送数据给服务端;
  •   服务端收到数据后就可以连续不断的先客户端发送数据;                                                                

2 、WebSocket数据帧格式

RFC 6455: The WebSocket Protocol (rfc-editor.org)

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+
  • FIN:该位为1表示收到的是最后一个数据帧;
  • RSV1, RSV2, RSV3:用于扩展,缺省为0
  • Opcode:操作代码指示所携带的数据类型Payload Data
    • 第一个数据帧FIN为0表示不是最后一个数据帧Opcode为1表示携带的数据的文本数据;
    • 第二个数据帧FIN为0表示不是最后一个数据帧Opcode为0表示为连接的帧;
    • 第三个数据帧FIN为1表示最后一个数据帧Opcode为0表示为连接的帧;
    • ping pong操作;用于保持没有操作时的心跳保持TCP连接;
  • Mask:掩码如果为1会使用 Masking-key对Payload Data进行掩码操作;用于网络安全
  • Payload length:负载长度;

十、WebSocket脚本

1、浏览器编写脚本读取服务器数据

  • WebSocket服务端可以通过WebSocket Client客户端,或者浏览器访问;

1.1 服务端

  • 服务端的修改的接收发送代码,首先将浏览器发来的数据接收打印出来,再用for循环隔三秒发送一个字符串;
static esp_err_t echo_handler(httpd_req_t *req)
{
    ...
    ...
    ...

    if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
        strcmp((char *)ws_pkt.payload, "Trigger async") == 0)
    {
        free(buf);
        return trigger_async_send(req->handle, req);
    }
    ESP_LOGI(TAG, "Packet data: %s", ws_pkt.payload); // 打印出收到的数据
    // 发送回数据
    for (int i = 0; i < 10; i++)
    {
        char char_arry[10];
        sprintf(char_arry, "i=%d", i + 1);
        
        ws_pkt.payload = (uint8_t *)char_arry;
        ws_pkt.type = HTTPD_WS_TYPE_TEXT;

        ret = httpd_ws_send_frame(req, &ws_pkt);
        if (ret != ESP_OK)
        {
            ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
        }
        else
        {
            ESP_LOGE(TAG, "Send successfully!");
        }
        vTaskDelay(3000 / portTICK_PERIOD_MS);
    }
    ESP_LOGE(TAG, "Finished sending!");
    free(buf);
    return ret;
}
  • menuconfig 配置开启WebSocket

1.2 浏览器端

  • 编写HTML脚本向服务器发送数据,并接收服务发回的数据;
<!--
1、文件类型2、html的标签
    1.1:头部(做了定义javascript脚本:在这个脚本下定义WebSocketTese函数
        判断浏览器是否支持WebSocket;如果支持定义一个WebSocket对象ws;
        "ws://192.168.4.2/ws"为WebSocket服务器的地址连上热点时获得;
        在ws打开时用ws.send向服务器发送数据;ws.onmessage在浏览器收到
        数据时用received_msg接收数据并赋值给getElementById("demo")这个变量的ID显示到网页
    ) 
    1.2:主体
        显示收到的变量;第一个<div></div>块,做一个超级链接用于运行脚本的WebSocketTese函数,Run WebSocket Clinet用于显示
        点击这个字符串就会运行WebSocketTese;第二个<p ></p>段落,用于表示上面的demo元素demo data!为缺省显示在收到数据后就会变化;

-->
<!DOCTYPE HTML>
<html>
    <head>
        <script type="text/javascript">
            function WebSocketTese()
            {
                if("WebSocket" in window)
                {
                    var ws = new WebSocket("ws://192.168.4.2/ws");
                    ws.onopen = function()
                    {
                        ws.send("please send data")
                    };
                    ws.onmessage = function(evt)
                    {
                        var received_msg = evt.data;
                        document.getElementById("demo").innerHTML = received_msg;
                    };

                }
                else
                {
                    alert("The current browser donesn't support websocket!");
                }
            }

        </script>

    </head>
    <body>
        <div>
            <a href="javascript:WebSocketTese()"> Run WebSocket Clinet </a>
            <p id="demo"> demo data!</p>
        </div>
            
    </body>
</html>

1.3 ESP32作为客户端访问服务器

  • 例程文件
  • 工作流程
    • client_init:启动WebSocket客户端的Task;
    • register_event:注册WebSocket回调事件,并调用event_handler事件处理函数处理这些事件;
    • client_start:开启WebSocket的客户端;
    • send_text:发送数据;
    • client_close:发送完5次数据后关闭客户端连接;
    • client_destory:销毁客户端对象;
    • 在收到数据后复位定时器;
    • 一开始创建定时器定时器、二进制信号量;send_text发送5次数据后就等二进制信号量;每发一次数据给服务器,服务器就会返回一次数据在event_handler中接收每收到一次就会重置定时器一次;五次结束后当定时器到了后会调用shutdowm_signaler函数释放一个信号量;send_text收到信号量解除阻塞关闭并销毁客户端;

十 一、 MQTT 

1、 MQTT简介

  • MQTT架构

  • MQTT特点

2、 MQTT协议

  • MQTT的控制数据包由下属3个构成,也有的只含第一个部分如断开连接的数据包;
  • Fixed Header:固定的数据包头部
    • 决定数据包控制的类型packet type
  • Variable Header:可变的数据包头部
    • 根据数据控制包的类型packet type决定是否有该部分
    • 如有该部分可以确定数据包的一些设置packet setting,比如说这个数据包是含登录信息
  • Payload:负载数据部分     
    • 再根据数据包的设置packet setting决定是否有该部分   
    •  如有该部分可以携带负载数据,如用户名;

                            

3、 MQTT客户端

  • MQTT客户端启动一个定时器,定时器Publish方法发送一些数据给到服务端;服务端再传递数据到相应的客户端Subscribe订阅到相应的节点客户端中;这里的客户端即是发布数据的客户端又是收取数据的客户端;

  • 例程代码D:\Espressif\frameworks\esp-idf-v5.0\examples\protocols\mqtt\tcp
  • 修改的代码如下主要做了在MQTT回调函数中
    • 当MQTT_EVENT_CONNECTED即WIFI连接上后向客户端的会订阅"/topic/qos1"节点主题;然后创建一个5s的重复定时器mqtt_timer;
    • 在定时器到了之后客户端"/topic/qos1"节点会向服务器主题发布
    • 当MQTT_EVENT_DATA客户端扫到"/topic/qos1"节点返回的数据就打印出来
    • MQTT_EVENT_DISCONNECTED断开连接后就停止定时器

//----------------------------------------------------------------------
static TimerHandle_t mqtt_timer;
int iID = 0;
esp_mqtt_client_handle_t client = NULL;
//----------------------------------------------------------------------
static void mqtt_timer_callback(TimerHandle_t *timer)
{
    ESP_LOGE(TAG, "Time is up");
    int msg_id;
    char str_arr[10] = " ";
    sprintf(str_arr, "data%d", iID);
    iID++;
    if (10 == iID)
    {
        iID = 0;
    }
    if (client != NULL)
    {
        msg_id = esp_mqtt_client_publish(client, "/topic/qos1", str_arr, 0, 1, 0); //"/topic/qos1"节点主题发布
        ESP_LOGI(TAG, "sent publish successful, msg_id=%d data=%s", msg_id, str_arr);
    }
    else
    {
        ESP_LOGI(TAG, "client is null");
    }
}
//----------------------------------------------------------------------

static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id);
    esp_mqtt_event_handle_t event = event_data;
    client = event->client;
    int msg_id;
    switch ((esp_mqtt_event_id_t)event_id)
    {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");                        // 连接
        msg_id = esp_mqtt_client_subscribe(client, "/topic/qos1", 0); // 订阅"/topic/qos1"节点主题
        ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

        //创建定时器
        mqtt_timer = xTimerCreate("mqtt timer",5000/portTICK_PERIOD_MS,pdTRUE,NULL,mqtt_timer_callback);
        xTimerStart(mqtt_timer,portMAX_DELAY);
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
        //停止定时器
        xTimerStop(mqtt_timer,portMAX_DELAY);
        break;
    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_UNSUBSCRIBED:
        ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA: // 客户端收到对应节点的数据并打印出
        ESP_LOGI(TAG, "MQTT_EVENT_DATA");
        printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
        printf("DATA=%.*s\r\n", event->data_len, event->data);
        break;
    case MQTT_EVENT_ERROR:
        ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT)
        {
            log_error_if_nonzero("reported from esp-tls", event->error_handle->esp_tls_last_esp_err);
            log_error_if_nonzero("reported from tls stack", event->error_handle->esp_tls_stack_err);
            log_error_if_nonzero("captured as transport's socket errno", event->error_handle->esp_transport_sock_errno);
            ESP_LOGI(TAG, "Last errno string (%s)", strerror(event->error_handle->esp_transport_sock_errno));
        }
        break;
    default:
        ESP_LOGI(TAG, "Other event id:%d", event->event_id);
        break;
    }
}

十二、 ESP-Now

1、 ESP-Now是什么

  •  基于数据链路层的无连接wifi协议
  • 将传统的七层的TCP/IP,前5层合并为一层
    • 减少了数据传输的延迟,更快的相应时间
    • 更低的功耗
    • 兼容性好在传统WIFI和蓝牙的另外的通信补充方式
    • 长距离的数据沟通
    • 支持一对一、一对多、广播,中间不需要热点路由中转;

2、 ESP-Now数据帧格式

ESP-NOW - ESP32 - — ESP-IDF 编程指南 latest 文档 (espressif.com)

  • 最大发送数据为250个字节
  • 应用程序数据被封装在各个供应商的动作帧中,然后在无连接的情况下,从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。

 3、 获取MAC地址

杂项系统 API - ESP32 - — ESP-IDF 编程指南 latest 文档 (espressif.com)

ESP_MAC_WIFI_STA 和 ESP_MAC_WIFI_SOFTAP 是 ESP32 WiFi 芯片的 MAC 地址的两种模式。

  • ESP_MAC_WIFI_STA 表示 STA(Station)模式下的 MAC 地址,即用于连接 Wi-Fi路由器 或其他基础设施的模式;
  •  ESP_MAC_WIFI_SOFTAP 则表示 AP(Access Point)模式下的 MAC 地址,即用于创建 Wi-Fi 热点的模式。

ESP_MAC_BT 和 ESP_MAC_ETH 是 ESP32 SoC 的 Bluetooth 和 Ethernet 接口的 MAC 地址。

  • ESP_MAC_BT 用于 BlueTooth 模式,例如 BLE(Bluetooth Low Energy);
  • ESP_MAC_ETH 用于 Ethernet 模式,例如将 ESP32 作为 Ethernet 设备接入局域网中。

实例代码:D:\Espressif\frameworks\esp-idf-v5.0\examples\system\base_mac_address

I (332) BASE_MAC: Using "0xb0, 0xb2, 0x1c, 0xa8, 0x9c, 0xe4" as base MAC address
I (332) WIFI_STA MAC: 0xb0, 0xb2, 0x1c, 0xa8, 0x9c, 0xe4
I (342) SoftAP MAC: 0xb0, 0xb2, 0x1c, 0xa8, 0x9c, 0xe5
I (352) BT MAC: 0xb0, 0xb2, 0x1c, 0xa8, 0x9c, 0xe6
I (352) Ethernet MAC: 0xb0, 0xb2, 0x1c, 0xa8, 0x9c, 0xe7

4、发送数据

ESP-NOW - ESP32 - — ESP-IDF 编程指南 latest 文档 (espressif.com) 

例程代码:D:\Espressif\frameworks\esp-idf-v5.0\examples\wifi\espnow

  • 初始化、注册发送回调函数
  • 添加配对设备:配置接收设备的相关信息包含WIFIW的通道号、模式、是否加密,MAC地址为上面所获取到的;
  • 发送 ESP-NOW 数据:创建任务发送函数,发送完后会触发发送回调函数打印出是否发送成功;
static void example_espnow_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status)
{
    //判断发送状态是否成功
    if (status == ESP_NOW_SEND_SUCCESS)
    {
        ESP_LOGI(TAG, "ESP_NOW_SEND_SUCCESS");
    }
    else
    {
        ESP_LOGE(TAG, "ESP_NOW_SEND_FAIL");
    }
    
}

static uint8_t target_mac[ESP_NOW_ETH_ALEN] = {0xb0, 0xb2, 0x1c, 0xa8, 0x9c, 0xe4};//接收设备的WIFI_STA的MAC地址
char send_buf[] = "Hello I am little one";

static void example_espnow_task(void *pvParameter)
{
    while (1)
    {
        if (esp_now_send(target_mac,(uint8_t*)send_buf, sizeof(send_buf)) != ESP_OK)
        {
            ESP_LOGE(TAG, "Send error");
        }
        vTaskDelay(5000 / portTICK_PERIOD_MS);
    }

}

static esp_err_t example_espnow_init(void)
{

    /* Initialize ESPNOW and register sending and receiving callback function. */
    ESP_ERROR_CHECK(esp_now_init());                                   // 初始化
    ESP_ERROR_CHECK(esp_now_register_send_cb(example_espnow_send_cb)); // 注册发送回调函数

    /* Add broadcast peer information to peer list. */ // 配置接收设备的相关信息
    esp_now_peer_info_t *peer = malloc(sizeof(esp_now_peer_info_t));
    if (peer == NULL)
    {
        ESP_LOGE(TAG, "Malloc peer information fail");
        // vSemaphoreDelete(s_example_espnow_queue);
        esp_now_deinit();
        return ESP_FAIL;
    }
    memset(peer, 0, sizeof(esp_now_peer_info_t));
    peer->channel = CONFIG_ESPNOW_CHANNEL;
    peer->ifidx = ESPNOW_WIFI_IF;
    peer->encrypt = false;
    memcpy(peer->peer_addr, target_mac, ESP_NOW_ETH_ALEN);
    ESP_ERROR_CHECK(esp_now_add_peer(peer));
    free(peer);

    xTaskCreate(example_espnow_task, "example_espnow_task", 2048, send_param, 4, NULL); // 创建任务发送数据

    return ESP_OK;
}

void app_main(void)
{
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    example_wifi_init();
    example_espnow_init();
}

5、接收数据

  • 注册接收函数回调
  • 在回调函数中打印出接收到的数据
static void example_espnow_recv_cb(const uint8_t *mac_addr, const uint8_t *data, int len)
{
    
    ESP_LOGE(TAG, "Receive data = %s",data);
  
}

static esp_err_t example_espnow_init(void)
{

    /* Initialize ESPNOW and register sending and receiving callback function. */
    ESP_ERROR_CHECK(esp_now_init());                                   // 初始化
    ESP_ERROR_CHECK( esp_now_register_recv_cb(example_espnow_recv_cb) );// 注册接收数据回调函数

    return ESP_OK;
} 
  • idf.py fullclean 清楚掉原有的编译信息

6、广播数据

  • 将发送数据的MAC地址改为0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF(广播地址)
static uint8_t target_mac[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};//接收设备的WIFI_STA的MAC地址
char send_buf[] = "This is boradcast";

 

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值