目录
- 0. 前言
- 其他ESP-IDF文章
- 1. 前期准备
- 1.1头文件准备
- 1.2 http 服务器搭建
- 2. 连接 wifi
- 3.http访问任务
- 4. 完整代码
0. 前言
使用ESP32使用 wifi 访问 http 服务器
开发环境:ESP-IDF 4.2
操作系统:Ubuntu22.04
开发板:自制的ESP32-WROOM-32E
其他ESP-IDF文章
Windows下espidf的环境搭建(超详细,看完一定会!)
Ubuntu下ESP-IDF的环境搭建
基于Freertos的ESP-IDF开发——1.HelloWorld
基于Freertos的ESP-IDF开发——2.点亮一颗LED
基于Freertos的ESP-IDF开发——3.使用任务(上)
基于Freertos的ESP-IDF开发——3.使用任务(中)
基于Freertos的ESP-IDF开发——3.使用任务(下)
基于Freertos的ESP-IDF开发——4.使用任务的方式来点亮LED灯
基于Freertos的ESP-IDF开发——5.使用按键[不带消抖、带消抖、长按短按识别]
基于Freertos的ESP-IDF开发——6.使用DHT1温湿度传感器
基于Freertos的ESP-IDF开发——7.WS2812B彩色灯循环
1. 前期准备
1.1头文件准备
准备好头文件以及全局变量(wifi名和密码)
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
/* Wi-Fi网络SSID和密码 */
#define WIFI_SSID "Xiaomi_E417"
#define WIFI_PASSWORD "********"
/* 目标IP地址 */
#define DEST_IP "192.168.31.58"
/* 目标域名 */
#define DEST_DOMAIN "192.168.31.58"
/* 事件循环标签 */
static const char *TAG = "main";
1.2 http 服务器搭建
HTTP(Hyper Text Transfer Protocol)是一种用于传输超文本的协议。它是客户端和服务器之间通信的基础,是访问万维网的标准协议。HTTP 协议不涉及数据包的传输,只负责规定了客户端和服务器之间的通信格式。
HTTP 协议的主要特点包括:
支持客户端/服务器模式。客户端和服务器之间通过 HTTP 协议通信,从而实现信息传输。
使用面向连接或无连接的方式进行通信。无连接方式即每次请求服务时都要建立连接,请求结束即断开连接;而面向连接方式则是先建立连接,再发送请求和接收响应。大部分的 Web 服务都是采用无连接方式。
使用请求/响应模型。客户端发送请求,服务器接收请求并发送响应,响应包括状态码、响应头和实体。
采用ASCII码传输。HTTP 协议的传输数据都是 ASCII 码,并不支持二进制传输。
HTTP 采用明文传输数据。因此对于机密性要求高的信息,需要在传输层进行加密,如使用 HTTPS 协议。
HTTP 协议通常使用 TCP/IP 协议族进行数据传输,但是也可以使用其他协议。在 HTTP/2 标准出现之前,HTTP 通常是基于文本的,但是在 HTTP/2 中,采用了二进制格式,这样可以提高性能。
使用Python的Flask框架搭建一个简单的http服务器,它的默认端口是80,由于我们是Linux,需要使用root权限才能启用1024以下的端口,所以我需要为root用户安装Flask库
sudo pip3 install flask
windows用户请移步至此查看第三方库的安装方法:
Python第三方库安装——使用vscode、pycharm安装Python第三方库
安装好flask库之后编写简单的代码来实现http服务器的搭建:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return "This is IoT_H2's Blog!"
if __name__ == "__main__":
app.run(host='0.0.0.0',port = 80)
如此以来,就能当你访问以自身IP为域名,80为端口访问自身的时候,页面上就会有提示This is IoT_H2’s Blog!,host设置为0.0.0.0是为了使除自己之外的人也能访问
现在运行此段代码,然后在浏览器地址栏输入自身IP然后回车,看看会有什么样的结果:
可以看到出现了如我们预期的那样的结果.
现在来编写代码,让ESP32联网然后访问我们的本地服务器来获取这段内容
2. 连接 wifi
使用下面这段代码来连接wifi
/* Wi-Fi连接任务 */
static void wifi_connect_task(void *arg)
{
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
esp_wifi_connect();
vTaskDelete(NULL);
}
再创建一个中断函数用于wifi断线重连
/* Wi-Fi事件处理程序 */
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_STA_START) {
// 在STA模式下,启动Wi-Fi连接任务
xTaskCreate(&wifi_connect_task, "wifi_connect_task", 4096, NULL, 2, NULL);
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
// 如果Wi-Fi断开连接,重新连接
esp_wifi_connect();
}
}
3.http访问任务
static void network_access_task(void *arg)
{
struct sockaddr_in dest_addr;// sockaddr_in 用于定义Internet地址结构的结构体
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);// 目标IP
dest_addr.sin_family = AF_INET;//IPV4
dest_addr.sin_port = htons(80);//端口号
while (1) {
// 创建通信套接字
int sockfd = -1;
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 连接到目标地址
if (connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) == 0) {
// 发送数据
const char *message = "GET / HTTP/1.0\r\nHost: " DEST_DOMAIN "\r\n\r\n";
send(sockfd, message, strlen(message), 0);
// 接收响应数据
char buffer[1024];
int recvlen = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (recvlen > 0) {
buffer[recvlen] = 0;
ESP_LOGI(TAG, "%s", buffer);//串口打印缓冲区接受到的信息
}
}
// 关闭套接字
shutdown(sockfd, 0);
close(sockfd);
// 暂停一段时间再次尝试连接,即每隔5s访问一次
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
结构体**sockaddr_in **的解释如下:
struct sockaddr_in {
uint8_t sin_len; /* 结构体总长度 */
sa_family_t sin_family; /* 地址族(AF_INET) */
in_port_t sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址 */
char sin_zero[8]; /* 保留字节 */
};
sin_len: 结构体的总长度。
sin_family: 地址族,通常为AF_INET表示IPv4协议。
sin_port: 网络字节序的16位端口号。
sin_addr: 网络字节序的32位IP地址。
sin_zero是一个长度为8的保留字段,用于在结构体长度不足时增加填充。
4. 完整代码
除main函数,其他都已经在上面解释过,现在在app_main函数中进行任务创建:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"
/* Wi-Fi网络SSID和密码 */
#define WIFI_SSID "Xiaomi_E417"
#define WIFI_PASSWORD "703703703"
/* 目标IP地址 */
#define DEST_IP "192.168.31.58"
/* 目标域名 */
#define DEST_DOMAIN "192.168.31.58"
/* 事件循环标签 */
static const char *TAG = "main";
/* Wi-Fi连接任务 */
static void wifi_connect_task(void *arg)
{
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
esp_wifi_connect();
vTaskDelete(NULL);
}
/* 网络访问任务 */
static void network_access_task(void *arg)
{
struct sockaddr_in dest_addr;
dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(80);
while (1) {
// 创建通信套接字
int sockfd = -1;
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 连接到目标地址
if (connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) == 0) {
// 发送数据
const char *message = "GET / HTTP/1.0\r\nHost: " DEST_DOMAIN "\r\n\r\n";
send(sockfd, message, strlen(message), 0);
// 接收响应数据
char buffer[1024];
int recvlen = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (recvlen > 0) {
buffer[recvlen] = 0;
ESP_LOGI(TAG, "%s", buffer);
}
}
// 关闭套接字
shutdown(sockfd, 0);
close(sockfd);
// 暂停一段时间再次尝试连接
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
/* Wi-Fi事件处理程序 */
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_STA_START) {
// 在STA模式下,启动Wi-Fi连接任务
xTaskCreate(&wifi_connect_task, "wifi_connect_task", 4096, NULL, 2, NULL);
} else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
// 如果Wi-Fi断开连接,重新连接
esp_wifi_connect();
}
}
/* 应用程序入口 */
void app_main()
{
// 初始化非易失性存储(NVS)区域
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 初始化TCP/IP协议栈
tcpip_adapter_init();
// 创建默认事件循环
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 初始化Wi-Fi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 注册Wi-Fi事件处理程序
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_START, &wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &wifi_event_handler, NULL));
// 启动Wi-Fi
ESP_ERROR_CHECK(esp_wifi_start());
// 启动网络访问任务
xTaskCreate(&network_access_task, "network_access_task", 4096, NULL, 2, NULL);
}
效果演示:
你也可以让它变成获取实时时间: