今天是第8专题,主要内容是:导入ArduinoUZlib功能库,借助该库把从【和风天气】官网返回的经过Gzip压缩的JSON数据,进行解压缩和解析,解析后的数据包括天气实况信息和天气预报信息,并将天气信息显示在 TFT 屏幕上。
JSON天气信息解压缩功能实现,请参照第 7 专题的内容。本文主要对解析【和风天气】JSON的数据结构和主要函数作个简要介绍。
最后,我们给出了项目实现的全部源代码和运行结果展示。
一、请求、解压缩和解析JSON信息的数据结构和功能函数
关于获取和解析天气信息JSON数据的数据结构和函数,都封装在weather_xinzhi.h文件中,其中主要包括 2 个数据结构 和 4 个功能 函数。具体实现代码如下。
1、实况天气数据结构:struct weather_now_data{},用于保存解析成功后的实况天气信息。
// 实时天气
struct weather_now_data
{
int code = -1; // API状态码,具体含义请参考状态码
String updateTime = ""; // 当前API的最近更新时间
String now_obsTime = ""; // 数据观测时间
String now_temp = "0"; // 温度,默认单位:摄氏度
int now_feelsLike = 0; // 体感温度,默认单位:摄氏度
String now_icon = ""; // 天气状况和图标的代码,图标可通过天气状况和图标下载
String now_text = ""; // 天气状况的文字描述,包括阴晴雨雪等天气状态的描述
String now_wind360 = "-1"; // 风向360角度
String now_windDir = ""; // 风向
String now_windScale = "-1"; // 风力等级
int now_windSpeed = -1; // 风速,公里/小时
int now_humidity = -1; // 相对湿度,百分比数值
int now_precip = -1; // 当前小时累计降水量,默认单位:毫米
int now_pressure = -1; // 大气压强,默认单位:百帕
int now_vis = -1; // 能见度,默认单位:公里
} wd;
2、天气预报数据结构:struct weather_tmr_data{},用于保存获取到的天气预报信息。
struct weather_tmr_data
{
String fxDate = ""; //"预报日期"
String sunrise = ""; //"日出时间"
String sunset = ""; //"日落时间"
String moonrise = ""; //"月升时间"
String moonset = ""; //"月落时间"
String moonPhase = ""; //"月相名称"
String moonPhaseIcon = ""; //"月相图标代码,图标可通过天气状况和图标下载"
String tempMax = ""; //"预报当天最高温度"
String tempMin = ""; //"预报当天最低温度"
String iconDay = ""; //"预报白天天气状况的图标代码,图标可通过天气状况和图标下载"
String textDay = ""; //"预报白天天气状况文字描述,包括阴晴雨雪等天气状态的描述"
String iconNight = ""; //"预报夜间天气状况的图标代码,图标可通过天气状况和图标下载"
String textNight = ""; //"预报晚间天气状况文字描述,包括阴晴雨雪等天气状态的描述"
String wind360Day = ""; //"预报白天风向360角度"
String windDirDay = ""; //"预报白天风向"
String windScaleDay = ""; //"预报白天风力等级"
String windSpeedDay = ""; //"预报白天风速,公里/小时"
String wind360Night = ""; //"预报夜间风向360角度"
String windDirNight = ""; //"预报夜间当天风向"
String windScaleNight = ""; //"预报夜间风力等级"
String windSpeedNight = ""; //"预报夜间风速,公里/小时"
String precip = ""; //"预报当天总降水量,默认单位:毫米"
String uvIndex = ""; //"紫外线强度指数"
String humidity = ""; //"相对湿度,百分比数值"
String pressure = ""; //"大气压强,默认单位:百帕"
String vis = ""; //"能见度,默认单位:公里"
};
weather_tmr_data wtd[3];
3、获取实况天气函数:void get_now_Weather(),实现请求和解压缩JSON数据功能。
// 获取实时天气数据
void get_now_Weather()
{
// 检查WIFI是否连接
if ((WiFi.status() == WL_CONNECTED))
{
// 准备发起请求
std::unique_ptr<BearSSL::WiFiClientSecure> client(new BearSSL::WiFiClientSecure);
client->setInsecure();
Serial.print("[HTTPS] begin...\n");
HTTPClient https;
if (https.begin(*client, "https://devapi.qweather.com/v7/weather/now?key=" + key + "&location=" + cityid))
{
https.addHeader("Accept-Encoding", "gzip");
Serial.print("[HTTPS] GET...\n");
// start connection and send HTTP header
int httpCode = https.GET();
if (httpCode > 0)
{
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK)
{
// get length of document (is -1 when Server sends no Content-Length header)
int len = https.getSize();
// create buffer for read
static uint8_t buff[128] = {0};
// read all data from server
int offset = 0;
// 为什么这里分配内存会报错?
// if(inbuf==NULL) inbuf=(uint8_t*)malloc(sizeof(uint8_t)*128);
while (https.connected() && (len > 0 || len == -1))
{
// get available data size
size_t size = client->available();
if (size)
{
// read up to 128 byte
int c = client->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
memcpy(buffer + offset, buff, sizeof(uint8_t) * c);
offset += c;
// write it to Serial
// Serial.write(buff, c);
if (len > 0)
{
len -= c;
}
}
delay(1);
}
readBytesSize = offset;
delay(1000);
if (readBytesSize)
{
// write it to Serial
Serial.write(buffer, readBytesSize);
Serial.println("");
uint32_t out_size = 0;
ArduinoUZlib::decompress(buffer, readBytesSize, outbuffer, out_size);
Serial.write(outbuffer, out_size);
// 调用解析函数
JsonDocument doc;
DeserializationError err = deserializeJson(doc, outbuffer);
if (err.code() == DeserializationError::Ok)
{
get_now_weather_data(doc);
}
else
{
Serial.println("数据解析出错");
}
}
else
{
Serial.println("no avali size!");
}
if (outbuffer != NULL)
{
free(outbuffer);
outbuffer = NULL;
}
}
}
else
{
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
}
else
{
Serial.printf("Unable to connect\n");
}
}
}
4、获取天气预报函数:void get_tmr_Weather(),请求和解压缩天气预报JSON数据。
// 获取天气预报数据
void get_tmr_Weather()
{
// 检查WIFI是否连接
if ((WiFi.status() == WL_CONNECTED))
{
// 准备发起请求
std::unique_ptr<BearSSL::WiFiClientSecure> client(new BearSSL::WiFiClientSecure);
client->setInsecure();
Serial.print("[HTTPS] begin...\n");
HTTPClient https;
if (https.begin(*client, "https://devapi.qweather.com/v7/weather/3d?key=" + key + "&location=" + cityid))
{
https.addHeader("Accept-Encoding", "gzip");
Serial.print("[HTTPS] GET...\n");
// start connection and send HTTP header
int httpCode = https.GET();
if (httpCode > 0)
{
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK)
{
// get length of document (is -1 when Server sends no Content-Length header)
int len = https.getSize();
// create buffer for read
static uint8_t buff[128] = {0};
// read all data from server
int offset = 0;
// 为什么这里分配内存会报错?
// if(inbuf==NULL) inbuf=(uint8_t*)malloc(sizeof(uint8_t)*128);
while (https.connected() && (len > 0 || len == -1))
{
// get available data size
size_t size = client->available();
if (size)
{
// read up to 128 byte
int c = client->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
memcpy(buffer + offset, buff, sizeof(uint8_t) * c);
offset += c;
// write it to Serial
// Serial.write(buff, c);
if (len > 0)
{
len -= c;
}
}
delay(1);
}
readBytesSize = offset;
delay(1000);
if (readBytesSize)
{
// write it to Serial
Serial.write(buffer, readBytesSize);
Serial.println("");
uint32_t out_size = 0;
ArduinoUZlib::decompress(buffer, readBytesSize, outbuffer, out_size);
Serial.write(outbuffer, out_size);
// 调用解析函数
JsonDocument doc;
DeserializationError err = deserializeJson(doc, outbuffer);
if (err.code() == DeserializationError::Ok)
{
get_tmr_weather_data(doc);
}
else
{
Serial.println("数据解析出错");
}
}
else
{
Serial.println("no avali size!");
}
if (outbuffer != NULL)
{
free(outbuffer);
outbuffer = NULL;
}
}
}
else
{
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
}
else
{
Serial.printf("Unable to connect\n");
}
}
}
5、提取实况天气具体数值的函数:void get_now_weather_data(JsonDocument &doc)
void get_now_weather_data(JsonDocument &doc)
{
// 将数据保存到weahter_data 的结构体,方便后续调用
Serial.println("");
wd.code = doc["code"];
wd.updateTime = doc["updateTime"].as<String>().substring(0, 16);
wd.now_obsTime = doc["now"]["obsTime"].as<String>().substring(0, 16);
wd.updateTime.replace("T", " ");
wd.now_obsTime.replace("T", " ");
wd.now_temp = doc["now"]["temp"].as<String>();
wd.now_feelsLike = doc["now"]["feelsLike"].as<int>();
wd.now_icon = doc["now"]["icon"].as<String>();
wd.now_text = doc["now"]["text"].as<String>();
wd.now_wind360 = doc["now"]["wind360"].as<String>();
wd.now_windDir = doc["now"]["windDir"].as<String>();
wd.now_windScale = doc["now"]["windScale"].as<String>();
wd.now_windSpeed = doc["now"]["windSpeed"].as<int>();
wd.now_humidity = doc["now"]["humidity"].as<int>();
wd.now_precip = doc["now"]["precip"].as<int>();
wd.now_pressure = doc["now"]["pressure"].as<int>();
wd.now_vis = doc["now"]["vis"].as<int>();
}
6、提取天气预报具体数值的函数:void get_tmr_weather_data(JsonDocument &doc)
// 获取 JSON 解析后的键值,并保存到相应数据结构中
void get_tmr_weather_data(JsonDocument &doc)
{
Serial.println("");
Serial.println("");
String code = doc["code"].as<String>(); // "200"
Serial.print("code = ");
Serial.print(code);
String updateTime = doc["updateTime"].as<String>().substring(0, 16); // "2024-05-15T10:18+08:00"
wd.now_obsTime = doc["now"]["obsTime"].as<String>().substring(0, 16);
wd.updateTime.replace("T", " ");
for (uint8 i = 0; i < 3; i++)
{
// 将数据保存到weahter_data 的结构体,方便后续调用
wtd[i].fxDate = doc["daily"][i]["fxDate"].as<String>();
wtd[i].sunrise = doc["daily"][i]["sunrise"].as<String>();
wtd[i].sunset = doc["daily"][i]["sunset"].as<String>();
wtd[i].moonrise = doc["daily"][i]["moonrise"].as<String>();
wtd[i].moonset = doc["daily"][i]["moonset"].as<String>();
wtd[i].moonPhase = doc["daily"][i]["moonPhase"].as<String>();
wtd[i].moonPhaseIcon = doc["daily"][i]["moonPhaseIcon"].as<String>();
wtd[i].tempMax = doc["daily"][i]["tempMax"].as<String>();
wtd[i].tempMin = doc["daily"][i]["tempMin"].as<String>();
wtd[i].iconDay = doc["daily"][i]["iconDay"].as<String>();
wtd[i].textDay = doc["daily"][i]["textDay"].as<String>();
wtd[i].iconNight = doc["daily"][i]["iconNight"].as<String>();
wtd[i].textNight = doc["daily"][i]["textNight"].as<String>();
wtd[i].wind360Day = doc["daily"][i]["wind360Day"].as<String>();
wtd[i].windDirDay = doc["daily"][i]["windDirDay"].as<String>();
wtd[i].windScaleDay = doc["daily"][i]["windScaleDay"].as<String>();
wtd[i].windSpeedDay = doc["daily"][i]["windSpeedDay"].as<String>();
wtd[i].wind360Night = doc["daily"][i]["wind360Night"].as<String>();
wtd[i].windDirNight = doc["daily"][i]["windDirNight"].as<String>();
wtd[i].windScaleNight = doc["daily"][i]["windScaleNight"].as<String>();
wtd[i].windSpeedNight = doc["daily"][i]["windSpeedNight"].as<String>();
wtd[i].precip = doc["daily"][i]["precip"].as<String>();
wtd[i].uvIndex = doc["daily"][i]["uvIndex"].as<String>();
wtd[i].humidity = doc["daily"][i]["humidity"].as<String>();
wtd[i].pressure = doc["daily"][i]["pressure"].as<String>();
wtd[i].vis = doc["daily"][i]["vis"].as<String>();
}
二、项目源代码下载
百度网盘下载链接:WeatherClock_example_8, 提取码:evcb
友情提示:(1)请务必将 ssid 和 password 修改成您所在环境的名称和密码;(2)请务必将const String key 修改成您自己申请的和风天气API密钥。
三、运行效果展示
WeatherClock_example_8
下一专题,我们将为这个项目添加智能配网的功能,欢迎关注留言。
如您需要了解其它专题的内容,请点击下面的链接。
第一专题内容,请参考:连接点亮SPI-TFT屏幕和UI布局设计
第二专题内容,请参考:WIFI模式设置及连接
第三专题内容,请参考:连接SHT30传感器,获取并显示当前环境温湿度数据(I2C)
第四专题内容,请参考:通过NTPClient库获取实时网络时间并显示在TFT屏幕上
第五专题内容,请参考:获取关于城市天气实况和天气预报的JSON信息(心知天气版)
第六专题内容,请参考:解析天气信息JSON数据并显示在 TFT 屏幕上(心知天气版)
第七专题内容,请参考:和风天气API返回JSON数据信息的解压缩实现