1

ESP32 IDF 获取天气信息 - 浇筑菜鸟

 1 year ago
source link: https://www.cnblogs.com/jzcn/p/16827037.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、注册天气获取账号

我使用的知心天气,没有获取天气账号的小伙伴可以去注册一下,知心天气官网:https://www.seniverse.com/
取得天气获取的API后,可以直接在浏览器中访问测试一下,如下图所示:
2406897-20221026082851793-161484000.png
这里我就不赘述了,稍微花点信息就可以明白天气是怎么获取的了。

二、天气信息

获取到的天气格式是JSON的数据,直接在浏览器中不好观察,所以我将它整理了一下,如下所示:

{
	"results":[
		{
			"location":{
				"id":"WKEZD7MXE04F",
				"name":"贵阳",
				"country":"CN",
				"path":"贵阳,贵阳,贵州,中国",
				"timezone":"Asia/Shanghai",
				"timezone_offset":"+08:00"
			},
			"daily":[
				{
					"date":"2022-10-24",
					"text_day":"多云",
					"code_day":"4",
					"text_night":"多云",
					"code_night":"4",
					"high":"24",
					"low":"12",
					"rainfall":"0.00",
					"precip":"0.00",
					"wind_direction":"东南",
					"wind_direction_degree":"135",
					"wind_speed":"8.4",
					"wind_scale":"2",
					"humidity":"57"
				},
				{
					"date":"2022-10-25",
					"text_day":"多云",
					"code_day":"4",
					"text_night":"多云",
					"code_night":"4",
					"high":"24",
					"low":"14",
					"rainfall":"0.00",
					"precip":"0.00",
					"wind_direction":"南",
					"wind_direction_degree":"180",
					"wind_speed":"8.4",
					"wind_scale":"2",
					"humidity":"62"
				},
				{
					"date":"2022-10-26",
					"text_day":"阴",
					"code_day":"9",
					"text_night":"阵雨",
					"code_night":"10",
					"high":"24",
					"low":"13",
					"rainfall":"4.63",
					"precip":"0.94",
					"wind_direction":"南",
					"wind_direction_degree":"180",
					"wind_speed":"3.0",
					"wind_scale":"1",
					"humidity":"87"
				}
			],
			"last_update":"2022-10-24T08:00:00+08:00"
		}
	]
} 


其中有些格式可能看不知道什么意思,不要怕,看官方的注释,如下所示:


{
  "results": [
    {
      "location": {
        "id": "C23NB62W20TF",
        "name": "西雅图",
        "country": "US",
        "path": "西雅图,华盛顿州,美国",
        "timezone": "America/Los_Angeles",
        "timezone_offset": "-07:00"
      },
      "now": {
        "text": "多云", //天气现象文字
        "code": "4", //天气现象代码
        "temperature": "14", //温度,单位为c摄氏度或f华氏度
        "feels_like": "14", //体感温度,单位为c摄氏度或f华氏度
        "pressure": "1018", //气压,单位为mb百帕或in英寸
        "humidity": "76", //相对湿度,0~100,单位为百分比
        "visibility": "16.09", //能见度,单位为km公里或mi英里
        "wind_direction": "西北", //风向文字
        "wind_direction_degree": "340", //风向角度,范围0~360,0为正北,90为正东,180为正南,270为正西
        "wind_speed": "8.05", //风速,单位为km/h公里每小时或mph英里每小时
        "wind_scale": "2", //风力等级,请参考:http://baike.baidu.com/view/465076.htm
        "clouds": "90", //云量,单位%,范围0~100,天空被云覆盖的百分比 #目前不支持中国城市#
        "dew_point": "-12" //露点温度,请参考:http://baike.baidu.com/view/118348.htm #目前不支持中国城市#
      },
      "last_update": "2015-09-25T22:45:00-07:00" //数据更新时间(该城市的本地时间)
    }
  ]
} 

三、ESP32获取天气信息

这里我使用的是ESP-IDF环境,并且是通过 socket 的方式进行获取

  1. socket 通信思路如下图所示:

    2406897-20221026081357995-1543235192.png
  2. 创建socket连接

    函数 int socket(int domain, int type, int protocol)
    含义 函数socket()为通信创建一个端点,并为该套接字返回一个文件描述符。
    返回值 int,若发生错误则返回-1
    domain 表示需要创建的协议。
    如:AF_INET表示IPv4,
    AF_INET6表示IPv6,
    AF_UNIX表示本地套接字
    type 创建时,选择需要的通行方式,如:
    SOCK_STREAM表示TCP,
    SOCK_DGRAM表示UDP,
    SOCK_SEQPACKET表示可靠的顺序包服务,
    SOCK_RAW表示网络层上的原始协议
    protocol 表示指定要使用的实际传输协议
    最常见的有IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP等。
    如果填0(IPPRORO_IP)则根据前两个参数自动选择协议
    /* 创建套接字 */
    socket_handle = socket(dns_info->ai_family, dns_info->ai_socktype, 0); // 0(IPPROTO_IP)可以用来表示选择一个默认的协议。
    if(socket_handle < 0) {
        ESP_LOGE(TAG, "... Failed to allocate socket");
        close(socket_handle);
        freeaddrinfo(dns_info);
        false;
    }
    
    
  3. 连接 connect
    连接时需要用到服务器的信息,而获取天气信息是通过域名的方式获取的,在连接之前,我们需要使用getaddrinfo()函数进行DNS解析

    /* 域名解析 */
    int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &dns_info);
    if(err != 0 || dns_info == NULL) {
        ESP_LOGE(TAG, "DNS lookup failed err=%d dns_info=%p", err, dns_info);
        return false;
    }
    
    /* 连接服务器 */
    if(connect(socket_handle, dns_info->ai_addr, dns_info->ai_addrlen) != 0) {
        ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
        close(socket_handle);
        freeaddrinfo(dns_info);
        false;
    }
    
    
  4. 通过写数据,发送get请求

    /* 想缓冲区中写入服务请求信息 */
    if (write(socket_handle, REQUEST, strlen(REQUEST)) < 0) {
        ESP_LOGE(TAG, "... socket send failed");
        close(socket_handle);
        false;
    }
    
    
  5. 设置请求超时

    /* 设置请求超时 */
    struct timeval receiving_timeout;
    receiving_timeout.tv_sec = 5;
    receiving_timeout.tv_usec = 0;
    if (setsockopt(socket_handle, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout, sizeof(receiving_timeout)) < 0) 
    {
        ESP_LOGE(TAG, "... failed to set socket receiving timeout");
        close(socket_handle);
        false;
    }
    
  6. 通过读取数据,获取get响应数据

    bzero(weather_buf, buf_size);                                  // 将内存 weather_buf 前的 sizeof(weather_buf) 全部设置为0
    int read_size = read(socket_handle, weather_buf, buf_size-1);  // 从缓冲区中读取指定长度的数据,当缓冲区中内容小于指定长度时,read() 返回实际读取的数据长度,
    ESP_LOGI(TAG, "get weather is:   %s", weather_buf);           // 打印获取的天气信息
    
    

四、天气获取案例

#include "lvgl_weather_view.h"
#include "cJSON.h"
#include "../../wifi/wifi.h"

#include <string.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/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

/* 获取天气的地址 */
#define WEB_SERVER "api.seniverse.com"                              // 服务器域名
#define WEB_PORT "80"                                               // 服务器端口
#define WEB_PATH "https://api.seniverse.com/v3/weather/daily.json?key=xxxxxx=Guiyang&language=zh-Hans"          // 天气获取路径 

/* 存放json解析后的天气信息 */
static lvgl_user_weather_info_t user_weather_info = {0};

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

/**
 * @brief 获取天气数据
 * 
 * @param weather_buf 天气数据的存储空间
 * @param buf_size 存储空间的大小
 * @return true 获取成功
 * @return false 获取失败
 */
static bool get_weather_buf(char *weather_buf, size_t buf_size)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,                       // AF_INET表示IPv4,AF_INET6表示IPv6
        .ai_socktype = SOCK_STREAM,                 // SOCK_STREAM表示TCP、SOCK_DGRAM表示UDP、SOCK_RAW表示RAW
    };
    struct addrinfo *dns_info;                      // DNS 解析信息
    int socket_handle;                              // socket句柄

    /* 域名解析 */
    int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &dns_info);
    if(err != 0 || dns_info == NULL) {
        ESP_LOGE(TAG, "DNS lookup failed err=%d dns_info=%p", err, dns_info);
        return false;
    }

    /* 打印解析的服务器 IP */
    struct in_addr *service_IP = &((struct sockaddr_in *)dns_info->ai_addr)->sin_addr;
    ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*service_IP));

    /* 创建套接字 */
    socket_handle = socket(dns_info->ai_family, dns_info->ai_socktype, 0);            // 0(IPPROTO_IP)可以用来表示选择一个默认的协议。
    if(socket_handle < 0) {
        ESP_LOGE(TAG, "... Failed to allocate socket");
        close(socket_handle);
        freeaddrinfo(dns_info);
        false;
    }
    // ESP_LOGI(TAG, "allocated socket... ");

    /* 连接服务器 */
    if(connect(socket_handle, dns_info->ai_addr, dns_info->ai_addrlen) != 0) {
        ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
        close(socket_handle);
        freeaddrinfo(dns_info);
        false;
    }
    // ESP_LOGI(TAG, "... connected");

    /* 释放 dns_info 指向的空间 */
    freeaddrinfo(dns_info);  

    /* 想缓冲区中写入服务请求信息 */
    if (write(socket_handle, REQUEST, strlen(REQUEST)) < 0) {
        ESP_LOGE(TAG, "... socket send failed");
        close(socket_handle);
        false;
    }
    // ESP_LOGI(TAG, "... socket send success");


    /* 设置请求超时 */
    struct timeval receiving_timeout;
    receiving_timeout.tv_sec = 5;
    receiving_timeout.tv_usec = 0;
    if (setsockopt(socket_handle, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout, sizeof(receiving_timeout)) < 0) 
    {
        ESP_LOGE(TAG, "... failed to set socket receiving timeout");
        close(socket_handle);
        false;
    }
    // ESP_LOGI(TAG, "... set socket receiving timeout success");

    /* 从缓冲区中读取天气信息 */
    bzero(weather_buf, buf_size);                                                   // 将内存 weather_buf 前的 sizeof(weather_buf) 全部设置为0
    int read_size = read(socket_handle, weather_buf, buf_size-1);                   // 从缓冲区中读取指定长度的数据,当缓冲区中内容小于指定长度时,read() 返回实际读取的数据长度,
    ESP_LOGI(TAG, "get weather is:   %s", weather_buf);                            // 打印获取的天气信息

    ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", read_size, errno);     // 打印读取到的数据长度
    close(socket_handle);
    return true;
}

五、JSON数据解析

/**
 * @brief 解析天气数据(JSON)
 * 
 * @param analysis_buf 数据的存储空间
 * @return true 解析成功
 * @return false 解析失败
 */
static bool parse_json_data(const char *analysis_buf)
{
    cJSON   *json_data = NULL;
    /* 截取有效json */
    char *index = strchr(analysis_buf, '{');
    // strcpy(weather_buf, index);
    
    json_data = cJSON_Parse(index);
    if( json_data == NULL ) // 判断字段是否json格式
    {
        return false;
    }  

    // ESP_LOGI(TAG, "Start parsing data");   
    cJSON* cjson_item =cJSON_GetObjectItem(json_data,"results");
    cJSON* cjson_results =  cJSON_GetArrayItem(cjson_item,0);

    /* 获取天气的地址 */ 
    cJSON* cjson_location = cJSON_GetObjectItem(cjson_results,"location");
    cJSON* cjson_temperature_name = cJSON_GetObjectItem(cjson_location,"name");
    strcpy(user_weather_info.location_name,cjson_temperature_name->valuestring);

    /* 天气信息 */
    cJSON* cjson_daily = cJSON_GetObjectItem(cjson_results,"daily");

    /* 当天的天气信息 */
    cJSON* cjson_daily_1 =  cJSON_GetArrayItem(cjson_daily,0);

    ESP_LOGI(TAG, "day_one_code is: %s", cJSON_GetObjectItem(cjson_daily_1,"code_day")->valuestring); 
    ESP_LOGI(TAG, "day_one_temp_high is: %s", cJSON_GetObjectItem(cjson_daily_1,"high")->valuestring); 
    ESP_LOGI(TAG, "day_three_temp_low is: %s", cJSON_GetObjectItem(cjson_daily_1,"low")->valuestring); 
    ESP_LOGI(TAG, "day_one_humi is: %s", cJSON_GetObjectItem(cjson_daily_1,"humidity")->valuestring); 
    ESP_LOGI(TAG, "day_one_windspeed is: %s", cJSON_GetObjectItem(cjson_daily_1,"wind_speed")->valuestring); 

注意:解析JSON数据时,使用的都是 valuestring 数据类型,否则会出现无法解析的现象

ESP32学习笔记(12)——JSON接口使用:https://blog.csdn.net/qq_36347513/article/details/116481167
ESP32学习笔记(14)——HTTP服务器 - 简书:https://www.jianshu.com/p/aa865ff71b05
ESP32_IDF学习8【HTTP服务器】 - redlightASl - 博客园:https://www.cnblogs.com/redlightASl/p/15542579.html
ESP32 之 ESP-IDF 教学(十二)WiFi篇—— LwIP 之 TCP 通信:https://blog.csdn.net/m0_50064262/article/details/120265731>


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK