本文最后更新于 2025-01-10T22:20:34+08:00
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 ); if (len < 0 ) { ESP_LOGE(TAG, "recv failed: errno %d" , errno); break ; } else { rx_buffer[len] = 0 ; 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 ; ESP_LOGI(TAG, "Received %d bytes: %s" , len, rx_buffer); 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) 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 ; 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 ; } 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 )); 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 ; socklen_t socklen = sizeof (source_addr); int len = recvfrom(sock, rx_buffer, sizeof (rx_buffer) - 1 , 0 , (struct sockaddr *)&source_addr, &socklen); if (len < 0 ) { ESP_LOGE(TAG, "recvfrom failed: errno %d" , errno); break ; } else { rx_buffer[len] = 0 ; 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) { 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 ; 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 if (len < 0 ) { ESP_LOGE(TAG, "recvfrom failed: errno %d" , errno); break ; } else { 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 ; 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 ; ESP_LOGI(TAG, "Starting server on port: '%d'" , config.server_port); if (httpd_start(&server, &config) == ESP_OK) { 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服务的功能。主要包括以下几个步骤:
初始化HTTPD配置,使用HTTPD_DEFAULT_CONFIG()函数可以获得默认的HTTPD配置对象,这里设置了LRU清理使能;
启动HTTPD服务器,通过调用httpd_start()函数启动HTTPD服务器,参数server指向创建后的HTTPD句柄,config包含HTTPD的配置信息;
注册URI处理函数,通过调用httpd_register_uri_handler()函数将URI处理函数添加到HTTP服务器的URI处理列表中,可以实现不同URI地址的访问和处理;
注册基本认证机制,如果开启了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, .user_ctx = "Hello World!" };
这段代码是一个基于ESP32的HTTP服务器程序中的URI处理函数,对应的URI地址为/hello。具体来说:
.uri表示了要处理的URI地址,即当客户端请求该地址时,调用相应的处理函数进行处理;
.method表示了该URI地址所支持的HTTP请求方法,本例中为HTTP_GET,即只支持GET请求;
.handler表示了处理函数,当客户端发起请求时,调用该处理函数进行响应;
.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; buf_len = httpd_req_get_hdr_value_len(req, "Host" ) + 1 ; if (buf_len > 1 ) { buf = malloc (buf_len); 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); } 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 ]; 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); } httpd_resp_set_hdr(req, "Custom-Header-1" , "Custom-Value-1" ); httpd_resp_set_hdr(req, "Custom-Header-2" , "Custom-Value-2" ); const char * resp_str = (const char *) req->user_ctx; httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN); if (httpd_req_get_hdr_value_len(req, "Host" ) == 0 ) { ESP_LOGI(TAG, "Request headers lost" ); } return ESP_OK; }
这段代码是一个基于ESP32的HTTP服务器程序中的处理GET请求的函数,对应的URI地址为/hello。具体来说:
首先定义了一个名为hello_get_handler的函数,该函数接收一个指向httpd_req_t类型的指针参数req,表示HTTP请求的结构体实例;
函数中通过httpd_req_get_hdr_value_len和httpd_req_get_hdr_value_str等函数获取HTTP请求头中特定字段的值,比如Host、Test-Header-1、Test-Header-2等;
接着通过httpd_req_get_url_query_len和httpd_req_get_url_query_str等函数获取URL查询字符串,并从中解析出特定键值对的值,比如query1、query2、query3等;
通过httpd_resp_set_hdr设置一些自定义的响应头;
最后调用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 static esp_err_t echo_post_handler (httpd_req_t *req) { char buf[100 ]; int ret, remaining = req->content_len; while (remaining > 0 ) { if ((ret = httpd_req_recv(req, buf, MIN(remaining, sizeof (buf)))) <= 0 ) { if (ret == HTTPD_SOCK_ERR_TIMEOUT) { continue ; } return ESP_FAIL; } httpd_resp_send_chunk(req, buf, ret); remaining -= ret; ESP_LOGI(TAG, "=========== RECEIVED DATA ==========" ); ESP_LOGI(TAG, "%.*s" , ret, buf); ESP_LOGI(TAG, "====================================" ); } 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 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' ) { ESP_LOGI(TAG, "Unregistering /hello and /echo URIs" ); httpd_unregister_uri(req->handle, "/hello" ); httpd_unregister_uri(req->handle, "/echo" ); 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); httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, NULL ); } httpd_resp_send(req, NULL , 0 ); return ESP_OK; }
这段代码是一个HTTP PUT请求处理函数,主要用于实现URI处理器(URI handlers)的实时注册和注销。具体来说:
首先接收HTTP请求的数据并存储到buf中;
判断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错误处理函数;
最后通过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 #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 ; } 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" ); 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 请求的代码。主要实现了:
定义了 WEB_SERVER、WEB_PORT 和 WEB_PATH 三个常量,分别表示服务器地址、端口号和请求路径。
使用 getaddrinfo 函数查询服务器的 IP 地址,并对返回值进行判断处理。
创建一个套接字,并通过 connect 函数连接服务器。
将 HTTP GET 请求通过 write 函数发送给服务器。
设置接收超时时间并通过 read 函数接收服务器的响应报文,将其逐个字符地打印到控制台上。
关闭套接字并延时一段时间后重复执行上述流程。
整个过程中使用了一些 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 一般包括了以下几个字段:
FIN:1 位,表示当前帧是否为消息的最后一帧,值为 1 表示是,值为 0 表示不是。
RSV1、RSV2、RSV3:各占 1 位,用于扩展协议。通常情况下,这三位都被置为 0。
Opcode:4 位,用于表示消息类型,如 0x1 表示文本消息,0x2 表示二进制消息,0x8 表示关闭连接等。
Mask:1 位,表示负载数据是否被掩码处理过,值为 1 表示被掩码处理,值为 0 表示未被掩码处理。
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; 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) { 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; 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; 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 typedef struct httpd_ws_frame { bool final; bool fragmented; httpd_ws_type_t type; uint8_t *payload; size_t len; } 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; } uint8_t tx_len = 0 ; uint8_t header_buf[10 ] = {0 }; header_buf[0 ] |= (!frame->fragmented) ? HTTPD_WS_FIN_BIT : (frame->final? HTTPD_WS_FIN_BIT: HTTPD_WS_CONTINUE); header_buf[0 ] |= frame->type; if (frame->len <= 125 ) { header_buf[1 ] = frame->len & 0x7f U; tx_len = 2 ; } else if (frame->len > 125 && frame->len < UINT16_MAX) { header_buf[1 ] = 126 ; header_buf[2 ] = (frame->len >> 8U ) & 0xff U; header_buf[3 ] = frame->len & 0xff U; tx_len = 4 ; } else { header_buf[1 ] = 127 ; uint8_t shift_idx = sizeof (uint64_t ) - 1 ; uint64_t len64 = frame->len; for (int8_t idx = 2 ; idx <= 9 ; idx++) { header_buf[idx] = (len64 >> (shift_idx * 8 )) & 0xff U; shift_idx--; } tx_len = 10 ; } header_buf[1 ] &= (~HTTPD_WS_MASK_BIT); struct sock_db *sess = httpd_sess_get(hd, fd); if (!sess) { return ESP_ERR_INVALID_ARG; } 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; } 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 连接发送加密后的帧数据。主要实现如下:
首先对 frame 参数进行校验,如果为空则返回 ESP_ERR_INVALID_ARG 错误。
根据 WebSocket 数据帧格式,设置 Header 的 FIN、Opcode 以及 Payload length 字段。同时根据负载长度的大小决定 Header 的长度,最多为 10 个字节,包括了 2 个字节的 Header,2 个字节的 Payload length,4 个字节的 Masking key(在此代码中未使用,因为 WebSocket Server 不需要对 Payload 进行掩码处理)。
通过 httpd_sess_get() 函数获取对应 WebSocket 连接的套接字描述符(socket descriptor),若获取失败则返回 ESP_ERR_INVALID_ARG 错误。
调用 socket 的 send_fn() 函数,将 Header 和 Payload 发送给客户端。
函数返回 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 #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 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 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()); 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 #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); } }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); 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 esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); 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()); 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广播数据 参考链接