ESP32wifi入门

ESP32是Espressif乐鑫信息科技推出的一块WiFi芯片。

拥有40nm工艺、双核32位MCU、2.4GHz双模Wi-Fi和蓝牙芯片、主频高达230MHz,计算能力可达600DMIPS。

-涵盖精细分辨时钟门控、省电模式和动态电压调整等特征。

-它集成了天线和射频巴伦,功率放大器,低噪声放大器,滤波器和电源管理模块等元器件,性能稳定,易于制造,工作温度范围从-40℃到125℃。

-支持多种通信协议,如:I2C. I2S. SPI. UART. CAN.

-多种调节管理模式:Active模式、Modem-sleep模式、Light-sleep模式、Deep-sleep模式、Hibernation模式。可根据不同需求,调节所需方案。

ESP32 wifi入门

1.WiFi工作流程

WiFi热点工作流程


工作流程如下图所示:
示例程序位于:Espressif\frameworks\esp-idf-v4.4.3\examples\wifi\getting_started\softAP

WiFi设备工作流程

示例代码位于:`Espressif\frameworks\esp-idf-v4.4.3\examples\wifi\getting_started\station
与热点流程相似,但是细节有所不同

下面是连接wifi的流程:

2.TCP协议

tcp客户端

标准流程:
初始化-连接-数据交换-断开连接

客户端和服务端的具体细节:

示例代码:C:\Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\sockets\tcp_client

tcp_client_task 基本流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

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;



    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);

        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);

        //连接服务器

        int 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");



        //发送数据

        while (1) {

            //发送数据

            int err = send(sock, payload, strlen(payload), 0);

            if (err < 0) {

                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);

                break;

            }

            //接收数据

            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);

            }



            vTaskDelay(2000 / portTICK_PERIOD_MS);

        }



        if (sock != -1) {

            ESP_LOGE(TAG, "Shutting down socket and restarting...");

            //关闭套接字

            shutdown(sock, 0);

            //删除套接字

            close(sock);

        }

    }

    vTaskDelete(NULL);

}

tcp服务端:

参考例程:Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\sockets\tcp_server
tcp_server_task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
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);

    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));

    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);

    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);

        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);



        shutdown(sock, 0);

        close(sock);

    }



CLEAN_UP:

    close(listen_sock);

    vTaskDelete(NULL);

}

值得注意的一点:这里将整个tcpserver的流程放在一个task里,以至于他只能一对一通信,若要连接多个,则需要将连接,接收的部分也作为task来编写。

3. UDP协议

UDP客户端

TCP与UDP之间的差异:
流程:

特点及应用:

参考例程:C:\Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\sockets\udp_client
udp_client_task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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);

        if (sock < 0) {

            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);

            break;

        }

        ESP_LOGI(TAG, "Socket created, sending to %s:%d", HOST_IP_ADDR, PORT);



        while (1) {



            int err = sendto(sock, payload, strlen(payload), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));

            if (err < 0) {

                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);

                break;

            }

            ESP_LOGI(TAG, "Message sent");



            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);



            // Error occurred during receiving

            if (len < 0) {

                ESP_LOGE(TAG, "recvfrom 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 (strncmp(rx_buffer, "OK: ", 4) == 0) {

                    ESP_LOGI(TAG, "Received expected message, reconnecting");

                    break;

                }

            }



            vTaskDelay(2000 / portTICK_PERIOD_MS);

        }



        if (sock != -1) {

            ESP_LOGE(TAG, "Shutting down socket and restarting...");

            shutdown(sock, 0);

            close(sock);

        }

    }

    vTaskDelete(NULL);

}

UDP服务端:

参考例程:C:\Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\sockets\udp_server
udp_server_task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
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);

        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



        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", 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);

#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);

                }



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

                ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str);

                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);

}

4.HTTP协议

HTTP介绍:

HTTP(HyperText Transfer Protocol)是用于传输Web数据的应用层协议,它是基于TCP/IP协议栈之上的。HTTP协议的作用是规定客户端和服务器之间通讯的格式和内容,确保客户端能够正确向服务器请求数据,服务器也能够正确地响应请求并返回数据。

HTTP协议使用客户端/服务器模型。客户端向服务器发送HTTP请求,服务器接收到请求后,根据请求的内容进行一定的处理,并将处理结果封装在HTTP响应中返回给客户端。HTTP请求和响应都有一定的结构,通常包含三部分:起始行、首部和主体。



HTTP的起始行包含了请求方法或响应状态码等信息,如GET、POST等请求方法,200 OK、404 Not Found等响应状态码。HTTP的首部包含了请求或响应的各项属性,如Accept、Content-Type、Set-Cookie等。HTTP的主体则包含了请求或响应的具体内容,如HTML、JSON等文本数据或二进制数据。
请求:

响应:


HTTP协议也具有无状态性,即服务器不会保存客户端的任何状态信息,每个请求都是独立的,需要客户端通过某种方式将状态信息发送给服务器,如Cookie机制。

目前主流的HTTP版本为HTTP/1.1和HTTP/2,其中HTTP/1.1采用串行的方式发送和接收数据,而HTTP/2支持多路复用,可以同时发送多个请求和响应,性能更好。

URL格式:

URL(Uniform Resource Locator)是用于定位互联网上资源的地址,常见的资源包括网页、图片、视频等。URL由多个部分组成,包括协议、主机名、端口号、路径和查询字符串等。

一个标准的URL格式如下:

复制代码

1
<协议>://<主机名>:<端口>/<路径>?<查询字符串>

其中,协议表示访问资源所采用的协议,如HTTP、HTTPS、FTP等;主机名表示资源所在的主机名或IP地址;端口号表示与主机通信的端口号,默认情况下使用协议默认的端口;路径表示请求的资源路径,可以是一个具体的文件路径,也可以是一个文件夹路径;查询字符串表示请求参数,以问号(?)开头,多个参数之间用&符号分隔。

例如,以下是一个标准的URL格式:

复制代码

1
https://www.example.com:80/index.html?id=123&name=test

其中,协议为HTTPS,主机名为www.example.com,端口号为80(默认端口号可省略),路径为/index.html,查询字符串为id=123&name=test。

请求方法(以http-server为例):

示例代码:C:\Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\http_server\simple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
static httpd_handle_t start_webserver(void)

{

    httpd_handle_t server = NULL;

    httpd_config_t config = HTTPD_DEFAULT_CONFIG();

    config.lru_purge_enable = true;



    // Start the httpd server

    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);

    if (httpd_start(&server, &config) == ESP_OK) {

        // Set URI handlers

        ESP_LOGI(TAG, "Registering URI handlers");

        httpd_register_uri_handler(server, &hello);

        httpd_register_uri_handler(server, &echo);

        httpd_register_uri_handler(server, &ctrl);

        #if CONFIG_EXAMPLE_BASIC_AUTH

        httpd_register_basic_auth(server);

        #endif

        return server;

    }



    ESP_LOGI(TAG, "Error starting server!");

    return NULL;

}

这段代码是一个基于ESP32的HTTP服务器程序,通过调用httpd_start()函数启动HTTP服务器,并注册URI处理函数,来实现Web服务的功能。主要包括以下几个步骤:

  1. 初始化HTTPD配置,使用HTTPD_DEFAULT_CONFIG()函数可以获得默认的HTTPD配置对象,这里设置了LRU清理使能;

  2. 启动HTTPD服务器,通过调用httpd_start()函数启动HTTPD服务器,参数server指向创建后的HTTPD句柄,config包含HTTPD的配置信息;

  3. 注册URI处理函数,通过调用httpd_register_uri_handler()函数将URI处理函数添加到HTTP服务器的URI处理列表中,可以实现不同URI地址的访问和处理;

  4. 注册基本认证机制,如果开启了CONFIG_EXAMPLE_BASIC_AUTH宏定义,则通过httpd_register_basic_auth()函数在HTTPD服务器上注册基本的用户名密码验证机制。

这里的hello、echo、ctrl都是处理URI请求的处理函数,分别对应不同的URI地址。httpd_handle_t类型是HTTPD服务器的句柄类型,可以用于维护HTTPD服务器的状态等信息.

下面对这几个处理函数进行分析:

get方法

注册路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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!"

};

这段代码是一个基于ESP32的HTTP服务器程序中的URI处理函数,对应的URI地址为/hello。具体来说:

  1. .uri表示了要处理的URI地址,即当客户端请求该地址时,调用相应的处理函数进行处理;

  2. .method表示了该URI地址所支持的HTTP请求方法,本例中为HTTP_GET,即只支持GET请求;

  3. .handler表示了处理函数,当客户端发起请求时,调用该处理函数进行响应;

  4. .user_ctx是一个指向用户数据的指针,允许在处理函数中使用该指针来存储一些用户自定义的数据,这里将”Hello World!”传递给处理函数作为响应内容。

在这个例子中,当客户端通过GET方法请求URI地址/hello时,会调用hello_get_handler函数进行处理,函数的实现可以根据需求自行编写。

下面是处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
static esp_err_t hello_get_handler(httpd_req_t *req)

{

    char*  buf;

    size_t buf_len;



    /* Get header value string length and allocate memory for length + 1,

     * extra byte for null termination */

    buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;

    if (buf_len > 1) {

        buf = malloc(buf_len);

        /* Copy null terminated value string into buffer */

        if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {

            ESP_LOGI(TAG, "Found header => Host: %s", buf);

        }

        free(buf);

    }



    buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-2") + 1;

    if (buf_len > 1) {

        buf = malloc(buf_len);

        if (httpd_req_get_hdr_value_str(req, "Test-Header-2", buf, buf_len) == ESP_OK) {

            ESP_LOGI(TAG, "Found header => Test-Header-2: %s", buf);

        }

        free(buf);

    }



    buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-1") + 1;

    if (buf_len > 1) {

        buf = malloc(buf_len);

        if (httpd_req_get_hdr_value_str(req, "Test-Header-1", buf, buf_len) == ESP_OK) {

            ESP_LOGI(TAG, "Found header => Test-Header-1: %s", buf);

        }

        free(buf);

    }



    /* Read URL query string length and allocate memory for length + 1,

     * extra byte for null termination */

    buf_len = httpd_req_get_url_query_len(req) + 1;

    if (buf_len > 1) {

        buf = malloc(buf_len);

        if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {

            ESP_LOGI(TAG, "Found URL query => %s", buf);

            char param[32];

            /* Get value of expected key from query string */

            if (httpd_query_key_value(buf, "query1", param, sizeof(param)) == ESP_OK) {

                ESP_LOGI(TAG, "Found URL query parameter => query1=%s", param);

            }

            if (httpd_query_key_value(buf, "query3", param, sizeof(param)) == ESP_OK) {

                ESP_LOGI(TAG, "Found URL query parameter => query3=%s", param);

            }

            if (httpd_query_key_value(buf, "query2", param, sizeof(param)) == ESP_OK) {

                ESP_LOGI(TAG, "Found URL query parameter => query2=%s", param);

            }

        }

        free(buf);

    }



    /* Set some custom headers */

    httpd_resp_set_hdr(req, "Custom-Header-1", "Custom-Value-1");

    httpd_resp_set_hdr(req, "Custom-Header-2", "Custom-Value-2");



    /* Send response with custom headers and body set as the

     * string passed in user context*/

    const char* resp_str = (const char*) req->user_ctx;

    httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);



    /* After sending the HTTP response the old HTTP request

     * headers are lost. Check if HTTP request headers can be read now. */

    if (httpd_req_get_hdr_value_len(req, "Host") == 0) {

        ESP_LOGI(TAG, "Request headers lost");

    }
    return ESP_OK;
}

这段代码是一个基于ESP32的HTTP服务器程序中的处理GET请求的函数,对应的URI地址为/hello。具体来说:

  1. 首先定义了一个名为hello_get_handler的函数,该函数接收一个指向httpd_req_t类型的指针参数req,表示HTTP请求的结构体实例;

  2. 函数中通过httpd_req_get_hdr_value_len和httpd_req_get_hdr_value_str等函数获取HTTP请求头中特定字段的值,比如Host、Test-Header-1、Test-Header-2等;

  3. 接着通过httpd_req_get_url_query_len和httpd_req_get_url_query_str等函数获取URL查询字符串,并从中解析出特定键值对的值,比如query1、query2、query3等;

  4. 通过httpd_resp_set_hdr设置一些自定义的响应头;

  5. 最后调用httpd_resp_send发送HTTP响应,响应内容为req->user_ctx所指向的字符串,即在URI处理函数中传入的”Hello World!”。

post方法
1
2
3
4
5
6
7
8
9
10
11
12
static const httpd_uri_t echo = {

    .uri       = "/echo",

    .method    = HTTP_POST,

    .handler   = echo_post_handler,

    .user_ctx  = NULL

};

处理函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* An HTTP POST handler */

static esp_err_t echo_post_handler(httpd_req_t *req)

{

    char buf[100];

    int ret, remaining = req->content_len;



    while (remaining > 0) {

        /* Read the data for the request */

        if ((ret = httpd_req_recv(req, buf,

                        MIN(remaining, sizeof(buf)))) <= 0) {

            if (ret == HTTPD_SOCK_ERR_TIMEOUT) {

                /* Retry receiving if timeout occurred */

                continue;

            }

            return ESP_FAIL;

        }



        /* Send back the same data */

        httpd_resp_send_chunk(req, buf, ret);

        remaining -= ret;



        /* Log data received */

        ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");

        ESP_LOGI(TAG, "%.*s", ret, buf);

        ESP_LOGI(TAG, "====================================");

    }



    // End response

    httpd_resp_send_chunk(req, NULL, 0);

    return ESP_OK;

}

put方法:
1
2
3
4
5
6
7
8
9
10
11
12

static const httpd_uri_t ctrl = {

    .uri       = "/ctrl",

    .method    = HTTP_PUT,

    .handler   = ctrl_put_handler,

    .user_ctx  = NULL

};

处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/* An HTTP PUT handler. This demonstrates realtime

 * registration and deregistration of URI handlers

 */

static esp_err_t ctrl_put_handler(httpd_req_t *req)

{

    char buf;

    int ret;



    if ((ret = httpd_req_recv(req, &buf, 1)) <= 0) {

        if (ret == HTTPD_SOCK_ERR_TIMEOUT) {

            httpd_resp_send_408(req);

        }

        return ESP_FAIL;

    }



    if (buf == '0') {

        /* URI handlers can be unregistered using the uri string */

        ESP_LOGI(TAG, "Unregistering /hello and /echo URIs");

        httpd_unregister_uri(req->handle, "/hello");

        httpd_unregister_uri(req->handle, "/echo");

        /* Register the custom error handler */

        httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, http_404_error_handler);

    }

    else {

        ESP_LOGI(TAG, "Registering /hello and /echo URIs");

        httpd_register_uri_handler(req->handle, &hello);

        httpd_register_uri_handler(req->handle, &echo);

        /* Unregister custom error handler */

        httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, NULL);

    }



    /* Respond with empty body */

    httpd_resp_send(req, NULL, 0);

    return ESP_OK;

}

这段代码是一个HTTP PUT请求处理函数,主要用于实现URI处理器(URI handlers)的实时注册和注销。具体来说:

  1. 首先接收HTTP请求的数据并存储到buf中;
  2. 判断buf的值是否为’0’,如果是则调用httpd_unregister_uri函数注销/hello和/echo的URI处理器,同时调用httpd_register_err_handler函数注册了一个自定义的404错误处理函数http_404_error_handler;如果不是则通过httpd_register_uri_handler函数分别注册/hello和/echo的URI处理器,并取消注册自定义的404错误处理函数;
  3. 最后通过httpd_resp_send函数回复空的响应消息体。

总之,该函数可实现URI处理器的实时注册和注销,提高了HTTP请求处理的灵活性和可扩展性。

request请求(客户端)

示例代码:C:\Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\http_request
http_get_task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/* Constants that aren't configurable in menuconfig */

#define WEB_SERVER "example.com"

#define WEB_PORT "80"

#define WEB_PATH "/"



static const char *TAG = "example";



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";



static void http_get_task(void *pvParameters)

{

    const struct addrinfo hints = {

        .ai_family = AF_INET,

        .ai_socktype = SOCK_STREAM,

    };

    struct addrinfo *res;

    struct in_addr *addr;

    int s, r;

    char recv_buf[64];



    while(1) {

        int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);



        if(err != 0 || res == NULL) {

            ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res);

            vTaskDelay(1000 / portTICK_PERIOD_MS);

            continue;

        }



        /* Code to print the resolved IP.



           Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */

        addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;

        ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));



        s = socket(res->ai_family, res->ai_socktype, 0);

        if(s < 0) {

            ESP_LOGE(TAG, "... Failed to allocate socket.");

            freeaddrinfo(res);

            vTaskDelay(1000 / portTICK_PERIOD_MS);

            continue;

        }

        ESP_LOGI(TAG, "... allocated socket");



        if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {

            ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);

            close(s);

            freeaddrinfo(res);

            vTaskDelay(4000 / portTICK_PERIOD_MS);

            continue;

        }



        ESP_LOGI(TAG, "... connected");

        freeaddrinfo(res);



        if (write(s, REQUEST, strlen(REQUEST)) < 0) {

            ESP_LOGE(TAG, "... socket send failed");

            close(s);

            vTaskDelay(4000 / portTICK_PERIOD_MS);

            continue;

        }

        ESP_LOGI(TAG, "... socket send success");



        struct timeval receiving_timeout;

        receiving_timeout.tv_sec = 5;

        receiving_timeout.tv_usec = 0;

        if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,

                sizeof(receiving_timeout)) < 0) {

            ESP_LOGE(TAG, "... failed to set socket receiving timeout");

            close(s);

            vTaskDelay(4000 / portTICK_PERIOD_MS);

            continue;

        }

        ESP_LOGI(TAG, "... set socket receiving timeout success");



        /* Read HTTP response */

        do {

            bzero(recv_buf, sizeof(recv_buf));

            r = read(s, recv_buf, sizeof(recv_buf)-1);

            for(int i = 0; i < r; i++) {

                putchar(recv_buf[i]);

            }

        } while(r > 0);



        ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);

        close(s);

        for(int countdown = 10; countdown >= 0; countdown--) {

            ESP_LOGI(TAG, "%d... ", countdown);

            vTaskDelay(1000 / portTICK_PERIOD_MS);

        }

        ESP_LOGI(TAG, "Starting again!");

    }

}

这是一段 ESP32 上进行 HTTP GET 请求的代码。主要实现了:

  1. 定义了 WEB_SERVER、WEB_PORT 和 WEB_PATH 三个常量,分别表示服务器地址、端口号和请求路径。
  2. 使用 getaddrinfo 函数查询服务器的 IP 地址,并对返回值进行判断处理。
  3. 创建一个套接字,并通过 connect 函数连接服务器。
  4. 将 HTTP GET 请求通过 write 函数发送给服务器。
  5. 设置接收超时时间并通过 read 函数接收服务器的响应报文,将其逐个字符地打印到控制台上。
  6. 关闭套接字并延时一段时间后重复执行上述流程。

整个过程中使用了一些 ESP32 系统提供的函数和数据结构,如结构体 addrinfo、宏定义 AF_INET 和 SOCK_STREAM、函数 socket、close、read、write、getaddrinfo、freeaddrinfo、setsockopt 等。通过这些函数的调用,可以实现与远程服务器的通信,完成不同的网络应用。

5.webSocket协议

webSocket介绍

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它的主要特点是可以实现实时双向通信,并且可以在客户端和服务器之间传输任意数据,而不受 HTTP 语义限制。

与传统的 HTTP 请求-响应模式不同,WebSocket 采用了类似握手的方法来建立连接。客户端向服务器发送一个 HTTP 请求头,其中包含 Upgrade、Connection、Sec-WebSocket-Key 等字段,通知服务器要进行连接升级。服务器返回一个 HTTP 响应头,其中也包含 Upgrade、Connection 和 Sec-WebSocket-Accept 等字段,表示已同意升级连接。之后,客户端和服务器之间的数据就可以以帧为单位进行传输,每个帧包含一个信息片段,可以是文本也可以是二进制。

WebSocket 协议的优点是支持实时通信,具有较低的延迟,可减少网络传输的开销,有助于提高应用程序的性能。常见的应用场景包括聊天应用、在线游戏、实时数据监控等。

webSocket数据帧格式

WebSocket 数据帧是 WebSocket 协议中传输数据的基本单位,每个帧可以包含一个或多个信息片段(Message Fragment),可以是文本也可以是二进制,由 Header 和 Payload 两部分组成。

Header 一般包括了以下几个字段:

  1. FIN:1 位,表示当前帧是否为消息的最后一帧,值为 1 表示是,值为 0 表示不是。
  2. RSV1、RSV2、RSV3:各占 1 位,用于扩展协议。通常情况下,这三位都被置为 0。
  3. Opcode:4 位,用于表示消息类型,如 0x1 表示文本消息,0x2 表示二进制消息,0x8 表示关闭连接等。
  4. Mask:1 位,表示负载数据是否被掩码处理过,值为 1 表示被掩码处理,值为 0 表示未被掩码处理。
  5. Payload length:7 位或 7+16 位或 7+64 位,表示负载数据的长度。当值为 0~126 时,表示负载数据的长度就是该值;当值为 127 时,额外有两个字节表示长度,当值大于等于 2^16 时,额外有 4 个字节表示长度。
    如果 Mask 位被设置为 1,则 Header 中还要包含一个 4 字节的掩码码值(Masking Key),用于对负载数据进行反掩码操作。
    Payload 是实际的数据内容,长度由 Header 中的 Payload length 字段表示,可能是文本也可能是二进制。
    在整个 WebSocket 连接过程中,客户端和服务器会相互发送一些数据帧,包括握手请求、握手响应、消息片段等,根据不同的功能要求分别使用不同的 Opcode 来表示。

webSocket服务器

示例代码C:\Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\http_server\ws_echo_server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
static esp_err_t echo_handler(httpd_req_t *req)

{

    if (req->method == HTTP_GET) {

        ESP_LOGI(TAG, "Handshake done, the new connection was opened");

        return ESP_OK;

    }

    httpd_ws_frame_t ws_pkt;

    uint8_t *buf = NULL;

    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));

    ws_pkt.type = HTTPD_WS_TYPE_TEXT;

    /* Set max_len = 0 to get the frame len */

    esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);

    if (ret != ESP_OK) {

        ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);

        return ret;

    }

    ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);

    if (ws_pkt.len) {

        /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */

        buf = calloc(1, ws_pkt.len + 1);

        if (buf == NULL) {

            ESP_LOGE(TAG, "Failed to calloc memory for buf");

            return ESP_ERR_NO_MEM;

        }

        ws_pkt.payload = buf;

        /* Set max_len = ws_pkt.len to get the frame payload */

        ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);

        if (ret != ESP_OK) {

            ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);

            free(buf);

            return ret;

        }

        ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);

    }

    ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);

    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);

    }



    ret = httpd_ws_send_frame(req, &ws_pkt);

    if (ret != ESP_OK) {

        ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);

    }

    free(buf);

    return ret;

}



static const httpd_uri_t ws = {

        .uri        = "/ws",

        .method     = HTTP_GET,

        .handler    = echo_handler,

        .user_ctx   = NULL,

        .is_websocket = true

};
服务器握手协议

服务器接收数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    httpd_ws_frame_t ws_pkt;

    uint8_t *buf = NULL;

    memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));

    ws_pkt.type = HTTPD_WS_TYPE_TEXT;

    /* Set max_len = 0 to get the frame len */

    esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);

    if (ret != ESP_OK) {

        ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);

        return ret;

    }

    ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);

数据格式(和帧格式对应):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**

 * @brief WebSocket frame format

 */

typedef struct httpd_ws_frame {

    bool final;                 /*!< Final frame:

                                     For received frames this field indicates whether the `FIN` flag was set.

                                     For frames to be transmitted, this field is only used if the `fragmented`

                                         option is set as well. If `fragmented` is false, the `FIN` flag is set

                                         by default, marking the ws_frame as a complete/unfragmented message

                                         (esp_http_server doesn't automatically fragment messages) */

    bool fragmented;            /*!< Indication that the frame allocated for transmission is a message fragment,

                                     so the `FIN` flag is set manually according to the `final` option.

                                     This flag is never set for received messages */

    httpd_ws_type_t type;       /*!< WebSocket frame type */

    uint8_t *payload;           /*!< Pre-allocated data buffer */

    size_t len;                 /*!< Length of the WebSocket data */

} httpd_ws_frame_t;
服务器发送数据

异步发送

1
trigger_async_send(req->handle, req);

同步发送:

1
httpd_ws_send_frame(req, &ws_pkt);

这两个函数都调用esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame)
httpd_ws_send_frame_async:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
esp_err_t httpd_ws_send_frame_async(httpd_handle_t hd, int fd, httpd_ws_frame_t *frame)

{

    if (!frame) {

        ESP_LOGW(TAG, LOG_FMT("Argument is invalid"));

        return ESP_ERR_INVALID_ARG;

    }


    /* Prepare Tx buffer - maximum length is 14, which includes 2 bytes header, 8 bytes length, 4 bytes mask key */

    uint8_t tx_len = 0;

    uint8_t header_buf[10] = {0 };

    /* Set the `FIN` bit by default if message is not fragmented. Else, set it as per the `final` field */

    header_buf[0] |= (!frame->fragmented) ? HTTPD_WS_FIN_BIT : (frame->final? HTTPD_WS_FIN_BIT: HTTPD_WS_CONTINUE);

    header_buf[0] |= frame->type; /* Type (opcode): 4 bits */


    if (frame->len <= 125) {

        header_buf[1] = frame->len & 0x7fU; /* Length for 7 bits */

        tx_len = 2;

    } else if (frame->len > 125 && frame->len < UINT16_MAX) {

        header_buf[1] = 126;                /* Length for 16 bits */

        header_buf[2] = (frame->len >> 8U) & 0xffU;

        header_buf[3] = frame->len & 0xffU;

        tx_len = 4;

    } else {

        header_buf[1] = 127;                /* Length for 64 bits */

        uint8_t shift_idx = sizeof(uint64_t) - 1; /* Shift index starts at 7 */

        uint64_t len64 = frame->len; /* Raise variable size to make sure we won't shift by more bits

                                      * than the length has (to avoid undefined behaviour) */

        for (int8_t idx = 2; idx <= 9; idx++) {

            /* Now do shifting (be careful of endianness, i.e. when buffer index is 2, frame length shift index is 7) */

            header_buf[idx] = (len64 >> (shift_idx * 8)) & 0xffU;

            shift_idx--;

        }

        tx_len = 10;

    }
    /* WebSocket server does not required to mask response payload, so leave the MASK bit as 0. */

    header_buf[1] &= (~HTTPD_WS_MASK_BIT);



    struct sock_db *sess = httpd_sess_get(hd, fd);

    if (!sess) {

        return ESP_ERR_INVALID_ARG;

    }

    /* Send off header */

    if (sess->send_fn(hd, fd, (const char *)header_buf, tx_len, 0) < 0) {

        ESP_LOGW(TAG, LOG_FMT("Failed to send WS header"));

        return ESP_FAIL;

    }



    /* Send off payload */

    if(frame->len > 0 && frame->payload != NULL) {

        if (sess->send_fn(hd, fd, (const char *)frame->payload, frame->len, 0) < 0) {

            ESP_LOGW(TAG, LOG_FMT("Failed to send WS payload"));

            return ESP_FAIL;

        }

    }

    return ESP_OK;

}

这是一段 ESP32 上通过 WebSocket 发送帧的代码,该函数会向指定的 WebSocket 连接发送加密后的帧数据。主要实现如下:

  1. 首先对 frame 参数进行校验,如果为空则返回 ESP_ERR_INVALID_ARG 错误。
  2. 根据 WebSocket 数据帧格式,设置 Header 的 FIN、Opcode 以及 Payload length 字段。同时根据负载长度的大小决定 Header 的长度,最多为 10 个字节,包括了 2 个字节的 Header,2 个字节的 Payload length,4 个字节的 Masking key(在此代码中未使用,因为 WebSocket Server 不需要对 Payload 进行掩码处理)。
  3. 通过 httpd_sess_get() 函数获取对应 WebSocket 连接的套接字描述符(socket descriptor),若获取失败则返回 ESP_ERR_INVALID_ARG 错误。
  4. 调用 socket 的 send_fn() 函数,将 Header 和 Payload 发送给客户端。
  5. 函数返回 ESP_OK 表示发送数据成功,返回 ESP_FAIL 表示发送数据失败。

webSocket客户端

示例代码:C:\Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\websocket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/* ESP HTTP Client 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 <stdio.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "protocol_examples_common.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"

#include "esp_log.h"
#include "esp_websocket_client.h"
#include "esp_event.h"

#define NO_DATA_TIMEOUT_SEC 5

static const char *TAG = "WEBSOCKET";

static TimerHandle_t shutdown_signal_timer;
static SemaphoreHandle_t shutdown_sema;

static void shutdown_signaler(TimerHandle_t xTimer)
{
ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC);
xSemaphoreGive(shutdown_sema);
}

#if CONFIG_WEBSOCKET_URI_FROM_STDIN
static void get_string(char *line, size_t size)
{
int count = 0;
while (count < size) {
int c = fgetc(stdin);
if (c == '\n') {
line[count] = '\0';
break;
} else if (c > 0 && c < 127) {
line[count] = c;
++count;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}

#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */

static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
switch (event_id) {
case WEBSOCKET_EVENT_CONNECTED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
break;
case WEBSOCKET_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED");
break;
case WEBSOCKET_EVENT_DATA:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA");
ESP_LOGI(TAG, "Received opcode=%d", data->op_code);
if (data->op_code == 0x08 && data->data_len == 2) {
ESP_LOGW(TAG, "Received closed message with code=%d", 256*data->data_ptr[0] + data->data_ptr[1]);
} else {
ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr);
}
ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset);

xTimerReset(shutdown_signal_timer, portMAX_DELAY);
break;
case WEBSOCKET_EVENT_ERROR:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR");
break;
}
}

static void websocket_app_start(void)
{
esp_websocket_client_config_t websocket_cfg = {};

shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS,
pdFALSE, NULL, shutdown_signaler);
shutdown_sema = xSemaphoreCreateBinary();

#if CONFIG_WEBSOCKET_URI_FROM_STDIN
char line[128];

ESP_LOGI(TAG, "Please enter uri of websocket endpoint");
get_string(line, sizeof(line));

websocket_cfg.uri = line;
ESP_LOGI(TAG, "Endpoint uri: %s\n", line);

#else
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;

#endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */

ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);

esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);
esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client);

esp_websocket_client_start(client);
xTimerStart(shutdown_signal_timer, portMAX_DELAY);
char data[32];
int i = 0;
while (i < 5) {
if (esp_websocket_client_is_connected(client)) {
int len = sprintf(data, "hello %04d", i++);
ESP_LOGI(TAG, "Sending %s", data);
esp_websocket_client_send_text(client, data, len, portMAX_DELAY);
}
vTaskDelay(1000 / portTICK_RATE_MS);
}

xSemaphoreTake(shutdown_sema, portMAX_DELAY);
esp_websocket_client_close(client, portMAX_DELAY);
ESP_LOGI(TAG, "Websocket Stopped");
esp_websocket_client_destroy(client);
}

void app_main(void)
{
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("WEBSOCKET_CLIENT", ESP_LOG_DEBUG);
esp_log_level_set("TRANSPORT_WS", ESP_LOG_DEBUG);
esp_log_level_set("TRANS_TCP", ESP_LOG_DEBUG);

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());

websocket_app_start();
}


工作流程:

6.MQTT协议

MQTT介绍

MQTT官网
MQTT(Message Queuing Telemetry Transport)是一种轻量级的、基于发布-订阅模式的通信协议,主要应用于物联网(IoT)领域中设备间的通信。

MQTT 协议采用了简单的二进制编码格式,使得它适用于网络带宽较小、延迟较高、网络不稳定的环境下进行通信。同时,MQTT 提供了 QoS(Quality of Service)服务质量保证机制,支持三种不同的 QoS 等级:

  • QoS 0:最多发送一次消息,不提供可靠性保证。
  • QoS 1:至少发送一次消息,确保消息能到达接收方,但可能会重复。
  • QoS 2:恰好发送一次消息,确保消息能到达接收方且只接收一次。

MQTT 协议的特点包括:可扩展性好、开销小、易于实现、开源、支持多种编程语言等。

在 MQTT 协议中,存在两个主要的参与者:发布者和订阅者。发布者将消息发布到一个或多个主题(Topic)中,订阅者可以订阅感兴趣的主题,从而接收到发布者发送的消息。主题可以看作是某一个特定类型的消息的分类标准。

MQTT 协议的使用场景包括但不限于:智能家居、智能灯光、智能安防、农业物联网、工业物联网等。

优点
轻巧高效

MQTT 客户端非常小,需要的资源最少,因此可以在小型微控制器上使用。MQTT 消息头很小,可以优化网络带宽。

双向通信

MQTT 允许在设备到云和云到设备之间进行消息传递。这使得向事物组广播消息变得容易。

扩展到数百万个事物

MQTT 可以扩展以连接数百万个物联网设备。

可靠的消息传递

消息传递的可靠性对于许多物联网用例都很重要。这就是为什么 MQTT 有 3 个定义的服务质量级别:0 - 最多一次,1 - 至少一次,2 - 恰好一次

支持不可靠的网络

许多物联网设备通过不可靠的蜂窝网络连接。MQTT 对持久会话的支持减少了将客户端与代理重新连接的时间。

已启用安全性

MQTT 使使用 TLS 加密消息和使用现代身份验证协议(如 OAuth)对客户端进行身份验证变得容易。

MQTT包协议

MQTT 控制数据包的结构:

········
其余具体见规范文档:MQTT协议:mqtt-v5.0.pdf)

MQTT客户端

示例代码:C:\Espressif\frameworks\esp-idf-v4.4.3\examples\protocols\mqtt\tcp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/* MQTT (over TCP) 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 <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "esp_wifi.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "freertos/timers.h"

#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"

#include "esp_log.h"
#include "mqtt_client.h"

static const char *TAG = "MQTT_EXAMPLE";
//--------------------------------------------------------------、
//定时器
static timer_handle_t mqtt_timer;
int iID = 0;
esp_mqtt_client_handle_t client=NULL;
//定时器回调函数
static void mqtt_timer_callback(timer_handle_t xTimer);
{
ESP_LOGI(TAG, "MQTT Timer callback");
char str_arr[15]="";
sprintf(str_arr, "data %d", iID);
iID++;
if(IID > 10)
{
iID = 0;
}
if(client!=NULL)
{
int msg_id=esp_mqtt_client_publish(client, "/topic/qos1", str_arr, 0, 1, 0);
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", msg_id);
}
else
{
ESP_LOGI(TAG, "MQTT client is NULL");
}

}

//--------------------------------------------------------------
static void log_error_if_nonzero(const char *message, int error_code)
{
if (error_code != 0) {
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);

}
}

/*
* @brief Event handler registered to receive MQTT events
*
* This function is called by the MQTT client event loop.
*
* @param handler_args user data registered to the event.
* @param base Event base for the handler(always MQTT Base in this example).
* @param event_id The id for the received event.
* @param event_data The data for the event, esp_mqtt_event_handle_t.
*/
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);
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);

mqtt_timer = xTimerCreate("mqtt_timer", pdMS_TO_TICKS(5000), pdTRUE, NULL, mqtt_timer_callback);
timter_start(mqtt_timer, portMAX_DELAY);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
timter_stop(mqtt_timer, portMAX_DELAY);
break;

case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
//msg_id = esp_mqtt_client_publish(client, "/topic/qos0", "data", 0, 0, 0);
//ESP_LOGI(TAG, "sent publish successful, msg_id=%d", 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;
}
}

static void mqtt_app_start(void)
{
esp_mqtt_client_config_t mqtt_cfg = {
.uri = CONFIG_BROKER_URL,
};
#if CONFIG_BROKER_URL_FROM_STDIN
char line[128];

if (strcmp(mqtt_cfg.uri, "FROM_STDIN") == 0) {
int count = 0;
printf("Please enter url of mqtt broker\n");
while (count < 128) {
int c = fgetc(stdin);
if (c == '\n') {
line[count] = '\0';
break;
} else if (c > 0 && c < 127) {
line[count] = c;
++count;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
mqtt_cfg.uri = line;
printf("Broker url: %s\n", line);
} else {
ESP_LOGE(TAG, "Configuration mismatch: wrong broker url");
abort();
}
#endif /* CONFIG_BROKER_URL_FROM_STDIN */

esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
/* The last argument may be used to pass data to the event handler, in this example mqtt_event_handler */
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
}

void app_main(void)
{
ESP_LOGI(TAG, "[APP] Startup..");
ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());

esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE);
esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT_BASE", ESP_LOG_VERBOSE);
esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);
esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE);
esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE);

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());

mqtt_app_start();
}

这是一个 ESP32 的 MQTT 示例程序,用于通过 TCP 连接到 MQTT 代理。程序实现了连接、订阅、发布等 MQTT 协议常用功能,并且使用了定时器来定时发送消息。

程序的主要框架如下:

  • 初始化日志记录级别
  • 初始化 NVS 文件系统、网络接口、事件循环
  • 连接 Wi-Fi 或者以太网
  • 启动 MQTT 客户端
  • 注册 MQTT 事件处理回调函数
  • 在 MQTT_CONNECTED 事件中创建并启动定时器
  • 当接收到其他 MQTT 事件时,做相应的处理(如订阅成功、接收到数据等)

其中,定时器回调函数 mqtt_timer_callback 每隔 5 秒会向 /topic/qos1 主题发布一条消息,消息内容为 “data iID”,其中 iID 是递增的计数器,每发送 10 条消息后归零重新开始。如果 MQTT 客户端未连接则不进行操作。

7.ESP-NOW协议

ESP-NOW介绍

ESP-NOW 是一种由乐鑫公司定义的无连接 Wi-Fi 通信协议。在 ESP-NOW 中,应用程序数据被封装在各个供应商的动作帧中,然后在无连接的情况下,从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。 CTR 与 CBC-MAC 协议 (CCMP) 可用来保护动作帧的安全。ESP-NOW 广泛应用于智能照明、远程控制、传感器等领域。

ESP-NOW帧格式

ESP-NOW 使用各个供应商的动作帧传输数据,默认比特率为 1 Mbps。各个供应商的动作帧格式为:

MAC 报头 分类代码 组织标识符 随机值 供应商特定内容 FCS
24 字节 1 字节 3 字节 4 字节 7~257 字节 4 字节
  • 分类代码:分类代码字段可用于指示各个供应商的类别(比如 127)。
  • 组织标识符:组织标识符包含一个唯一标识符 (比如 0x18fe34),为乐鑫指定的 MAC 地址的前三个字节。
  • 随机值:防止重放攻击。
  • 供应商特定内容:供应商特定内容包含供应商特定字段,如下所示:
元素 ID 长度 组织标识符 类型 版本 正文
1 字节 1 字节 3 字节 1 字节 1 字节 0~250 字节
  • 元素 ID:元素 ID 字段可用于指示特定于供应商的元素。

  • 长度:长度是组织标识符、类型、版本和正文的总长度。

  • 组织标识符:组织标识符包含一个唯一标识符 (比如 0x18fe34),为乐鑫指定的 MAC 地址的前三个字节。

  • 类型:类型字段设置为 4,代表 ESP-NOW。

  • 版本:版本字段设置为 ESP-NOW 的版本。

  • 正文:正文包含 ESP-NOW 数据。

由于 ESP-NOW 是无连接的,因此 MAC 报头与标准帧略有不同。FrameControl 字段的 FromDS 和 ToDS 位均为 0。第一个地址字段用于配置目标地址。第二个地址字段用于配置源地址。第三个地址字段用于配置广播地址 (0xff:0xff:0xff:0xff:0xff:0xff)。

ESP-NOW发送数据

ESP-NOW接收数据

ESP-NOW广播数据

参考链接


ESP32wifi入门
https://www.duruofu.xyz/posts/14063/
作者
DuRuofu
发布于
2023年6月6日
更新于
2025年1月10日
许可协议