ESP32 IDF 获取天气信息

一、注册天气获取账号

我使用的知心天气,没有获取天气账号的小伙伴可以去注册一下,知心天气官网:https://www.seniverse.com/
取得天气获取的API后,可以直接在浏览器中访问测试一下,如下图所示:

ESP32 IDF 获取天气信息
这里我就不赘述了,稍微花点信息就可以明白天气是怎么获取的了。

二、天气信息

获取到的天气格式是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 通信思路如下图所示:
    ESP32 IDF 获取天气信息
  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;
}

  1. 连接 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;
}

  1. 通过写数据,发送get请求
/* 想缓冲区中写入服务请求信息 */
if (write(socket_handle, REQUEST, strlen(REQUEST)) < 0) {
    ESP_LOGE(TAG, "... socket send failed");
    close(socket_handle);
    false;
}

  1. 设置请求超时
/* 设置请求超时 */
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;
}
  1. 通过读取数据,获取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"

/* &#x83B7;&#x53D6;&#x5929;&#x6C14;&#x7684;&#x5730;&#x5740; */
#define WEB_SERVER "api.seniverse.com"                              // &#x670D;&#x52A1;&#x5668;&#x57DF;&#x540D;
#define WEB_PORT "80"                                               // &#x670D;&#x52A1;&#x5668;&#x7AEF;&#x53E3;
#define WEB_PATH "https://api.seniverse.com/v3/weather/daily.json?key=xxxxxx=Guiyang&language=zh-Hans"          // &#x5929;&#x6C14;&#x83B7;&#x53D6;&#x8DEF;&#x5F84;

/* &#x5B58;&#x653E;json&#x89E3;&#x6790;&#x540E;&#x7684;&#x5929;&#x6C14;&#x4FE1;&#x606F; */
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 &#x83B7;&#x53D6;&#x5929;&#x6C14;&#x6570;&#x636E;
 *
 * @param weather_buf &#x5929;&#x6C14;&#x6570;&#x636E;&#x7684;&#x5B58;&#x50A8;&#x7A7A;&#x95F4;
 * @param buf_size &#x5B58;&#x50A8;&#x7A7A;&#x95F4;&#x7684;&#x5927;&#x5C0F;
 * @return true &#x83B7;&#x53D6;&#x6210;&#x529F;
 * @return false &#x83B7;&#x53D6;&#x5931;&#x8D25;
 */
static bool get_weather_buf(char *weather_buf, size_t buf_size)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,                       // AF_INET&#x8868;&#x793A;IPv4&#xFF0C;AF_INET6&#x8868;&#x793A;IPv6
        .ai_socktype = SOCK_STREAM,                 // SOCK_STREAM&#x8868;&#x793A;TCP&#x3001;SOCK_DGRAM&#x8868;&#x793A;UDP&#x3001;SOCK_RAW&#x8868;&#x793A;RAW
    };
    struct addrinfo *dns_info;                      // DNS &#x89E3;&#x6790;&#x4FE1;&#x606F;
    int socket_handle;                              // socket&#x53E5;&#x67C4;

    /* &#x57DF;&#x540D;&#x89E3;&#x6790; */
    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;
    }

    /* &#x6253;&#x5370;&#x89E3;&#x6790;&#x7684;&#x670D;&#x52A1;&#x5668; 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));

    /* &#x521B;&#x5EFA;&#x5957;&#x63A5;&#x5B57; */
    socket_handle = socket(dns_info->ai_family, dns_info->ai_socktype, 0);            // 0&#xFF08;IPPROTO_IP&#xFF09;&#x53EF;&#x4EE5;&#x7528;&#x6765;&#x8868;&#x793A;&#x9009;&#x62E9;&#x4E00;&#x4E2A;&#x9ED8;&#x8BA4;&#x7684;&#x534F;&#x8BAE;&#x3002;
    if(socket_handle < 0) {
        ESP_LOGE(TAG, "... Failed to allocate socket");
        close(socket_handle);
        freeaddrinfo(dns_info);
        false;
    }
    // ESP_LOGI(TAG, "allocated socket... ");

    /* &#x8FDE;&#x63A5;&#x670D;&#x52A1;&#x5668; */
    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");

    /* &#x91CA;&#x653E; dns_info &#x6307;&#x5411;&#x7684;&#x7A7A;&#x95F4; */
    freeaddrinfo(dns_info);

    /* &#x60F3;&#x7F13;&#x51B2;&#x533A;&#x4E2D;&#x5199;&#x5165;&#x670D;&#x52A1;&#x8BF7;&#x6C42;&#x4FE1;&#x606F; */
    if (write(socket_handle, REQUEST, strlen(REQUEST)) < 0) {
        ESP_LOGE(TAG, "... socket send failed");
        close(socket_handle);
        false;
    }
    // ESP_LOGI(TAG, "... socket send success");

    /* &#x8BBE;&#x7F6E;&#x8BF7;&#x6C42;&#x8D85;&#x65F6; */
    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");

    /* &#x4ECE;&#x7F13;&#x51B2;&#x533A;&#x4E2D;&#x8BFB;&#x53D6;&#x5929;&#x6C14;&#x4FE1;&#x606F; */
    bzero(weather_buf, buf_size);                                                   // &#x5C06;&#x5185;&#x5B58; weather_buf &#x524D;&#x7684; sizeof(weather_buf) &#x5168;&#x90E8;&#x8BBE;&#x7F6E;&#x4E3A;0
    int read_size = read(socket_handle, weather_buf, buf_size-1);                   // &#x4ECE;&#x7F13;&#x51B2;&#x533A;&#x4E2D;&#x8BFB;&#x53D6;&#x6307;&#x5B9A;&#x957F;&#x5EA6;&#x7684;&#x6570;&#x636E;&#xFF0C;&#x5F53;&#x7F13;&#x51B2;&#x533A;&#x4E2D;&#x5185;&#x5BB9;&#x5C0F;&#x4E8E;&#x6307;&#x5B9A;&#x957F;&#x5EA6;&#x65F6;&#xFF0C;read() &#x8FD4;&#x56DE;&#x5B9E;&#x9645;&#x8BFB;&#x53D6;&#x7684;&#x6570;&#x636E;&#x957F;&#x5EA6;&#xFF0C;
    ESP_LOGI(TAG, "get weather is&#xFF1A;   %s", weather_buf);                            // &#x6253;&#x5370;&#x83B7;&#x53D6;&#x7684;&#x5929;&#x6C14;&#x4FE1;&#x606F;

    ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", read_size, errno);     // &#x6253;&#x5370;&#x8BFB;&#x53D6;&#x5230;&#x7684;&#x6570;&#x636E;&#x957F;&#x5EA6;
    close(socket_handle);
    return true;
}

</string.h>

五、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>

Original: https://www.cnblogs.com/jzcn/p/16827037.html
Author: 浇筑菜鸟
Title: ESP32 IDF 获取天气信息

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/800114/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球