本实验使用 ESP32 通过 WiFi 读取外网的城市气象信息,并显示到 LCD 显示屏。 这个实验的代码为工程“4_7_wifi_http”目录。
4.7.1. 实验内容
(1) 学习 NVS 使用。
(2) 学习 smartconfig 使用。
(3) 学习 DNS 域名解析。
(4) 学习 HTTP 请求数据,
(5) 学习 json 数据解析。
4.7.2. 实验简介
这个实验第一次启动的时候,会启动 smartconfig 一键配置 wifi 和密码,配对成功后保存到 NVS 里。 第二次启动,通过 NVS 读取 wifi 信息并连接 AP,当网络联接成功后,开始启动 DNS 域名解析,域名解析 成功后,得到气象服务器的 IP,使用 TCP 请求读出指定的城市气象信息并显示到 LCD 上。
4.7.3. HTTP 原理
HTTP 全称是 HyperText Transfer Protocol,也叫做超文本传输协议,它是互联网上应用最为广泛的 一种网络协议,所有的 WWW 文件都必须遵守这个标准。
HTTP 是一个客户端和服务器端请求和应答的标准(TCP)。客户端是终端用户,服务器端是网站。通 过使用 Web 浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为 80)的 HTTP 请求。
(1) http 请求头讲解
http 请求就是客户端对服务器端发起的请求,下面给出请求头的格式:
是请求方法,GET 和 POST 是最常见的 HTTP 方法,除此以外还包括 DELETE、HEAD、OPTIONS、 PUT、TRACE。
2 为请求对应的 URL 地址,它和报文头的 Host 属性组成完整的请求 URL。
3 是协议名称及版本号。
是 HTTP 的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客 户端的信息。
是报文体,它将一个页面表单中的组件值通过 param1=value1¶m2=value2 的键值对形式 编码成一个格式化串,它承载多个请求参数的数据。
下来我们以 win7 自带的 IE 浏览器来查看请求头:
以打开百度首页为例,然后在 IE 浏览器中按下 F12,打开”开发人员工具”,切换到”网络”标签,然 后刷新页面:
我们对着前面的请求头格式看,第一行里请求后面的值包括了 3 部分内容:请求方法 GET、URL 地 址(表示根目录)和协议名称及版本号 HTTP/1.1。其余部分是报文头,不带有报文体。
(2) HTTP 应答格式
服务器收到了客户端发来的 HTTP 请求后,根据 HTTP 请求中的动作要求,服务端做出具体的动作, 将结果回应给客户端,称为 HTTP 响应。数据主要由三部分组成:
协议状态:包括协议版本 Version、状态码 Status Code、回应短语
响应头:包括搭建服务器的软件,发送响应的时间,回应数据的格式等信息
响应正文:就是响应的具体数据
应答例子:
HTTP/1.1 200 OK
Server:Apache Tomcat/5.0.12 Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112
data(返回数据)
在我们的这个例子里,是通过 HTTP 请求心知天气的服务器,取得城市的天气信息,心知天气返回的数据是 json 格式,那么我们就需要使用到第三方的开源库 cJSON 了。ESP32 的 SDK 已经自带这些移植好的库了, 我们只需要直接使用即可。天气预报的数据格式如下所示:
{
"results":[{
"location":{ "id":"WS10730EM8EV",
"name":"Shenzhen",
"country":"CN", "path":"Shenzhen,Shenzhen,Guangdong,China", "timezone":"Asia/Shanghai", "timezone_offset":"+08:00"
},
"now":{
"text":"Cloudy",
"code":"4", "temperature":"26"
},
"last_update":"2019-11-01T10:09:00+08:00"
}]
}.
由上图的 json 数据可以看出,城市为 shenzhen,当前天气 cloudy,温度为 26 度。
4.7.4. 代码讲解
使用 vs code 展开本实验的工程目录,如下图:
我们的这个实验,主要代码有 main 目录下,文件夹 components 下是之前讲过的 LCD 和 LED 驱动文件。 下面按照程序启动的流程讲解。
(1) 开机读取 smartconfig 配置
在 app_smartConfig.c 里,有函数 read_Smartconfig()用于开机读取 smartconfig 配置的 wifi 信息,如果 读取不到,就需要进行 smartconfig 配置。
//读 smartconfig 配置 void read_Smartconfig()
{
uint32_t len=0;
//初始化 NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
//发现新版本
//擦除 ESP_ERROR_CHECK(nvs_flash_erase()); err = nvs_flash_init();
}
//打开,类似数据库的表
err = nvs_open(SMARTCONFIG_LIST, NVS_READWRITE, &my_handle); if (err != ESP_OK) {
ESP_LOGE(TAG, "read_Smartconfig NVS Error (%s)!\n", esp_err_to_name(err)); wifi_isConfig=0;
} else {
//读取,类似数据读字段对应的值
err = nvs_get_i8(my_handle, SMARTCONFIG_ISCONFIG, &wifi_isConfig); if(err==ESP_OK){
ESP_LOGI(TAG, "wifi_isConfig = %d\n", wifi_isConfig);
//名称 len=32;
err = nvs_get_str (my_handle, SMARTCONFIG_SSID, wifi_ssid, &len); if(err==ESP_OK) ESP_LOGI(TAG, "wifi_ssid = %s\n", wifi_ssid);
//密码 len=64;
err = nvs_get_str (my_handle, SMARTCONFIG_PASSWORD, wifi_password, &len); if(err==ESP_OK) ESP_LOGI(TAG, "wifi_password = %s\n", wifi_password);
}else{
wifi_isConfig=0;
}
//关闭 nvs_close(my_handle);
}
}
读取的数据保存在 app_smartConfig.c 里的全局变量里,变量定义如下:
(2) 程序启动
程序启动后,先是读取了 smartconfig 配置,然后初始化了 LCD 和 LED,接着根据 wifi_isConfig 决定是 否需要启动 smartconfig。如果是需要启动 smartconfig,那么就打开红灯,进入 smartconfig 流程;如果不需要启动
smartconfig,就进入 WiFi 的 STA 连接流程。
//用户函数入口,相当于 main 函数
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init()); tcpip_adapter_init(); read_Smartconfig();//读 smartconfig 配置 initLed();//LED IO 口初始化
//显示屏初始化以及显示相关的提示 Lcd_Init();
//根据 NVS 数据决定是否要进入 Smartconfig if(1!=wifi_isConfig){
led_red(LED_ON);//打开红灯,表示正在配置中 lcd_display(0);
startSmartconfig();//配置 wifi 进入 smartconfig
}else{
//启动 STA,连接 AP wifi_init_sta(); lcd_display(1);
}
}
(3) 启动 http 任务
不管是否启用了 smartconfig,当网络连接上之后,都会进入文件 app_smartConfig.c 里的 wifi 回调函数, 当 wifi 取得 IP 后,就启动 http 任务,关键代码如下:
//wifi 连接事件回调函数
static esp_err_t smartconfig_event_handler(void *ctx, system_event_t *event)
{
......
case SYSTEM_EVENT_STA_GOT_IP://获取 IP ESP_LOGI(TAG1, "SYSTEM_EVENT_STA_GOT_IP");
//启动 http 任务请求 extern void start_http(); start_http();
break;
......
}
void start_http()
{
xTaskCreate(http_get_task, "http_get_task", 1024*10, NULL, 10, NULL);
}
......
(4) http 任务
首先我们看 app_http.c 的文件开始地方,给出了 http 请求头的信息,文档里写的城市是深圳(shenzhen), 大家可以根据自己所在的城市修改,如下图:
//http 组包宏,获取天气的 http 接口参数
#define WEB_SERVER "api.thinkpage.cn"
#define WEB_PORT "80"
#define WEB_URL "/v3/weather/now.json?key="
#define host "api.thinkpage.cn"
#define APIKEY "g3egns3yk2ahzb0p"
#define city "shenzhen"
#define language "en"
//http 请求包
static const char *REQUEST="GET"WEB_URL""APIKEY"&location="city"&language="language" HTTP/1.1\r\n" "Host: "WEB_SERVER"\r\n"
"Connection: close\r\n" "\r\n";
接着我们看 http 请求任务,通过七步完成一次 HTTP 请求,其中第四步是发送 HTTP 请求头,第五步 是完成 HTTP 应答数据的读取,第六步是对返回的 JSON 数据解析。代码如下:
//http 请求任务
void http_get_task(void *pvParameters)
{
......
while(1) {
//第一步:DNS 域名解析
int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res); if(err != 0 || res == NULL) {
ESP_LOGE(HTTP_TAG, "DNS lookup failed err=%d res=%p\r\n", err, res); vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
}
//打印获取的 IP
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
//ESP_LOGI(HTTP_TAG, "DNS lookup succeeded. IP=%s\r\n", inet_ntoa(*addr));
//第二步:新建 socket
s = socket(res->ai_family, res->ai_socktype, 0); if(s < 0) {
ESP_LOGE(HTTP_TAG, "... Failed to allocate socket.\r\n"); close(s);
freeaddrinfo(res);
vTaskDelay(1000 / portTICK_PERIOD_MS); continue;
}
//第三步:连接 ip
if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {
ESP_LOGE(HTTP_TAG, "... socket connect failed errno=%d\r\n", errno); close(s);
freeaddrinfo(res);
vTaskDelay(4000 / portTICK_PERIOD_MS); continue;
}
freeaddrinfo(res);
//第四步:发送 http 包
if (write(s, REQUEST, strlen(REQUEST)) < 0) { ESP_LOGE(HTTP_TAG, "... socket send failed\r\n"); close(s);
vTaskDelay(4000 / portTICK_PERIOD_MS); continue;
}
//清缓存 memset(mid_buf,0,sizeof(mid_buf));
//第五步:获取 http 应答包 do {
bzero(recv_buf, sizeof(recv_buf));
r = read(s, recv_buf, sizeof(recv_buf)-1); strcat(mid_buf,recv_buf);
} while(r > 0);
ESP_LOGI(HTTP_TAG, "mid_buf=%s.\r\n", mid_buf);
//第六步:json 解析 cjson_to_struct_info(mid_buf);
//第七步:关闭 socket,http 是短连接 close(s);
//延时一会
for(int countdown = 10; countdown >= 0; countdown--) { vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
}
JSON 数据解析是 http 请求的第 6 步,数据解析之后会从 LCD 显示出来,我们解析是城市、天气、温 度和时间。关于天气应答的格式,在上一节的“http 应答格式”里有例子,代码如下:
//解析 json 数据 只处理:解析 城市 天气 温度
//text:json 字符串
void cjson_to_struct_info(char *text)
{
char lcd_buff[100]={0};
cJSON *root,*psub; cJSON *arrayItem;
//截取有效 json
char *index=strchr(text,'{'); strcpy(text,
root = cJSON_Parse(text);
memset(&weathe, 0, sizeof(weather_info));
if(root!=NULL)
{
psub = cJSON_GetObjectItem(root, "results"); arrayItem = cJSON_GetArrayItem(psub,0);
cJSON *locat = cJSON_GetObjectItem(arrayItem, "location"); cJSON *now = cJSON_GetObjectItem(arrayItem, "now"); if((locat!=NULL)&&(now!=NULL))
{
psub=cJSON_GetObjectItem(locat,"name"); sprintf(weathe.weather_city,"%s",psub->valuestring);//城市 ESP_LOGI(HTTP_TAG,"city:%s",weathe.weather_city);
psub=cJSON_GetObjectItem(now,"text"); sprintf(weathe.weather_text,"%s",psub->valuestring);//天气 ESP_LOGI(HTTP_TAG,"weather:%s",weathe.weather_text);
psub=cJSON_GetObjectItem(now,"temperature"); sprintf(weathe.temperatur,"%s",psub->valuestring);//温度 ESP_LOGI(HTTP_TAG,"temperatur:%s",weathe.temperatur);
}
cJSON *d = cJSON_GetObjectItem(arrayItem, "last_update");//时间 if(d!=NULL){
sprintf(weathe.last_update,"%s",d->valuestring); ESP_LOGI(HTTP_TAG,"last_update:%s",weathe.last_update);
}
//LCD 显示 sprintf(lcd_buff,"city:%s",weathe.weather_city); LCD_P6x8Str(0,80,WHITE,BLACK,(u8 *)lcd_buff);
sprintf(lcd_buff,"weather:%s",weathe.weather_text); LCD_P6x8Str(0,90,WHITE,BLACK,(u8 *)lcd_buff);
sprintf(lcd_buff,"temperatur:%s",weathe.temperatur); LCD_P6x8Str(0,100,WHITE,BLACK,(u8 *)lcd_buff); LCD_P6x8Str(0,110,WHITE,BLACK,(u8 *)weathe.last_update);
}
cJSON_Delete(root);
}
4.7.5. 实验过程
配置下载串口、波特率、编绎和程序下载的详细过程请往回看 3.1.4,在这个实验里都是一笔带过。 (1) 把开发板通过 USB 线接到电脑上,通过设备管理器查看生成的串口。开发板在我们演示电脑上生
成的是 COM3。
(2) 在 menuconfig 菜单里配置下载程序串口。提供的例程配置的串口是 COM3,波特率为 921600。
(3) 通过 make all 编绎工程。
(4) 当编绎通过之后,使用命令 make flash 把程序下载到开发板上。或者参考 2.3.2 节,使用工具下载。
(5) 使用串口工具打开开发板生成的串口,默认的波特率是 115200。 串口工具在目录:.\开发软件\串口工具-sscom32.rar。
(6) 打开按下开发板的复位键,让程序运行起来。第一次启动开发板上应该是亮起红灯,此时需要按 照 4.3.5 里的手机一键配置 smartconfig,先配置 ESP32 的 wifi 名字和密码。如果开发板的蓝灯 亮起表示 wifi 已经正确连接。
最后推荐一款开发套件,可以手淘扫码查看。