今天是第七专题,主要内容是:导入ArduinoUZlib功能库,借助该库把从【和风天气】官网返回的经过Gzip压缩的JSON数据,进行解压缩和t解析,在串口监视器上输出解析后的JSON信息。
如您需要了解其它专题的内容,请点击下面的链接。
第一专题内容,请参考:连接点亮SPI-TFT屏幕和UI布局设计
第二专题内容,请参考:WIFI模式设置及连接
第三专题内容,请参考:连接SHT30传感器,获取并显示当前环境温湿度数据(I2C)
第四专题内容,请参考:通过NTPClient库获取实时网络时间并显示在TFT屏幕上
第五专题内容,请参考:获取关于城市天气实况和天气预报的JSON信息(心知天气版)
第六专题内容,请参考:解析天气信息JSON数据并显示在 TFT 屏幕上(心知天气版)
一、【心知天气】官网JSON数据特点
1、订阅模式。和风天气开发服务采用订阅模式,当你创建项目时,至少需要选择一种订阅。使用和风天气的服务订阅非常简单和自由,免费或者你只需要为你实际使用的部分付费。现有三种订阅模式:免费订阅、标准订阅、高级订阅。
每种订阅模式均可获取完整的天气信息数据。区别主要体现在数据请求量和更新频率方面。
2、经过压缩的JSON数据。通过官网API接口返回的JSON数据,是进行Gzip压缩后的JSON数据,客户端需要先进行解压缩,然后再借助ArduinoJson功能库解析出解压缩后JSON数据,比心知天气返回JSON数据增加了一个解压缩过程。
3、获取和风天气数据的方法。如果需要获得通过官网API接口返回的天气信息数据,需要首先心知天气注册账号、创建项目、选择订阅模式,然后获取API访问密钥KEY。具体操作方法网上有很多文档可供参考,请自行查询,比如:如何获取和风天气Web API的KEY?
二、添加ArduinoUZlib功能库
1. ArduinoUZlib 的功能。该库是从uzlib移植到Arduino框架的功能库,主要用来解压https请求服务器返回的gzip数据。这个库占用内存比较小。
2. 添加库方法。该库暂时无法从 PlatformIO 界面 -------> Libraries 加入。
具体方法是:(1)进入该库 Github 网站,下载 zip 压缩文件。
(3)将此文件夹复制到 项目目录下的 lib 文件夹内,就可以使用该库的全部功能了。
三、主要功能函数
该库使用方法简单方便,主要是通过调用 ArduinoUZlib::decompress(inbuff, size, outbuf,outsize) 这个功能函数来实现。
size_t size = stream->available(); // 还剩下多少数据没有读完?
uint8_t inbuff[size]; // 准备一个数组来装流数据,有多少装多少
stream->readBytes(inbuff, size); // 将http流数据写入inbuff中
uint8_t *outbuf=NULL; //解压后的输出流
uint32_t outsize=0; // 解压后多大?在调用解压方法后会被赋值。
// 调用解压函数
int result=ArduinoUZlib::decompress(inbuff, size, outbuf,outsize);
// 输出解密后的数据到控制台。
Serial.write(outbuf,outsize);
详细内容可参考该库 example 目录下的示例:StreamHttpsClientGzipDemo,用来解压缩返回的 gzip数据。
/**
StreamHTTPClient.ino
Created on: 24.05.2015
*/
#include <ArduinoJson.h>
#include <Arduino.h>
#include "ArduinoUZlib.h"
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
ESP8266WiFiMulti WiFiMulti;
// ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getHeapSize(), ESP.getMaxAllocHeap()
void heap(){
Serial.print("FREE_HEAP[");
Serial.print(ESP.getFreeHeap());
Serial.print("]\n");
}
void setup() {
Serial.begin(115200);
// Serial.setDebugOutput(true);
Serial.println();
Serial.println();
Serial.println();
for (uint8_t t = 4; t > 0; t--) {
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
WiFi.mode(WIFI_STA);
WiFiMulti.addAP("ssid", "password");
}
void log(const char *str) {
Serial.println(str);
}
static uint8_t buffer[1280]={0};
size_t readBytesSize=0;
void fetchBuffer() {
if ((WiFiMulti.run() == 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://192.168.2.144:8082/test")) {
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;
Serial.println("allocate");
// 为什么这里分配内存会报错?
// 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));
// int c = client->readBytes(buff, size);
// Serial.println("memcpy");
memcpy(buffer+offset, buff, sizeof(uint8_t)*c);
offset+=c;
if(c>0 && c!=16) {
log("======rb====");
Serial.printf("%d,", buff[c-3]);
Serial.printf("%d,", buff[c-2]);
Serial.printf("%d,", buff[c-1]);
log("\n======rb end====");
}
// write it to Serial
// Serial.write(buff, c);
if (len > 0) {
len -= c;
}
}
delay(1);
}
readBytesSize=offset;
Serial.printf("offset=%d\n", offset);
Serial.write(buffer, offset);
Serial.print("[HTTPS] connection closed or file end.\n");
}
} else {
Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
}
https.end();
} else {
Serial.printf("Unable to connect\n");
}
}
}
void loop() {
uint8_t *outbuf1=NULL;
// wait for WiFi connection
fetchBuffer();
Serial.printf("\nAfter fetch, buffer size=%d\n", readBytesSize);
delay(1000);
if(readBytesSize) {
// write it to Serial
log("===buf===");
Serial.printf("%d,", readBytesSize-3);
Serial.printf("%d,", readBytesSize-2);
Serial.printf("%d,", readBytesSize-1);
log("\n===buf end===");
Serial.write(buffer,readBytesSize);
uint32_t out_size=0;
int result=ArduinoUZlib::decompress(buffer, readBytesSize, outbuf1, out_size);
printf("outsize=%d, result=\n", out_size,result);
parseJSON((char*)outbuf1, out_size);
// Serial.write(outbuf,out_size);
}else {
Serial.println("no avali size!");
}
if (outbuf1!=NULL){
free(outbuf1);
outbuf1=NULL;
}
Serial.println("Wait 10s before the next round...");
delay(5000);
}
void parseJSON(char *input, int inputLength) {
// char* input;
// size_t inputLength; (optional)
DynamicJsonDocument doc(6144);
DeserializationError error = deserializeJson(doc, input, inputLength);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
const char* code = doc["code"]; // "200"
const char* updateTime = doc["updateTime"]; // "2022-12-05T15:35+08:00"
const char* fxLink = doc["fxLink"]; // "http://hfx.link/1u0r1"
for (JsonObject hourly_item : doc["hourly"].as<JsonArray>()) {
// const char* hourly_item_fxTime = hourly_item["fxTime"]; // "2022-12-05T17:00+08:00", ...
const char* hourly_item_fxTime = hourly_item["fxTime"]; // "2022-12-05T17:00+08:00", ...
const char* hourly_item_temp = hourly_item["temp"]; // "15", "13", "13", "12", "11", "11", "10", "10", ...
Serial.printf("%s,", hourly_item_temp);
const char* hourly_item_icon = hourly_item["icon"]; // "100", "150", "150", "150", "150", "150", "150", ...
const char* hourly_item_text = hourly_item["text"]; // "晴", "晴", "晴", "晴", "晴", "晴", "晴", "多云", "多云", ...
const char* hourly_item_wind360 = hourly_item["wind360"]; // "22", "24", "30", "33", "33", "31", "30", ...
const char* hourly_item_windDir = hourly_item["windDir"]; // "东北风", "东北风", "东北风", "东北风", "东北风", "东北风", ...
const char* hourly_item_windScale = hourly_item["windScale"]; // "3-4", "3-4", "3-4", "3-4", "3-4", ...
const char* hourly_item_windSpeed = hourly_item["windSpeed"]; // "16", "16", "16", "16", "14", "14", ...
const char* hourly_item_humidity = hourly_item["humidity"]; // "57", "63", "63", "65", "66", "67", "68", ...
const char* hourly_item_pop = hourly_item["pop"]; // "1", "3", "6", "6", "6", "6", "6", "6", "7", "7", ...
const char* hourly_item_precip = hourly_item["precip"]; // "0.0", "0.0", "0.0", "0.0", "0.0", "0.0", ...
const char* hourly_item_pressure = hourly_item["pressure"]; // "1013", "1013", "1012", "1012", "1012", ...
const char* hourly_item_cloud = hourly_item["cloud"]; // "5", "5", "4", "4", "7", "9", "11", "33", "54", ...
const char* hourly_item_dew = hourly_item["dew"]; // "7", "6", "6", "6", "5", "5", "5", "5", "5", "4", ...
}
Serial.println();
JsonArray refer_sources = doc["refer"]["sources"];
const char* refer_sources_0 = refer_sources[0]; // "QWeather"
const char* refer_sources_1 = refer_sources[1]; // "NMC"
const char* refer_sources_2 = refer_sources[2]; // "ECMWF"
const char* refer_license_0 = doc["refer"]["license"][0]; // "CC BY-SA 4.0"
}
四、和风天气JSON数据的解压缩实现
这是个实现解压缩和风天气JSON数据的简单示例:(1)将服务器进行Gzip压缩后返回的JSON数据,接收并保存到缓冲区 buffer 中;(2)调用 ArduinoUZlib::decompress(inbuffer, size, outbuffer,outsize) 解压 buffer 中的经过压缩的JSON数据,同时将解压后JSON数据保存到输出 outbuffer 中;(3)调用 ArduinoJson 库的 deserializeJson(doc, outbuffer) 函数,对明文JSON数据进行解析,并保存到实况天气数据结构 wd 中,然后通过串口监视器输出。
具体内容,请仔细阅读下面的代码实现。
// 实时天气
struct weather_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;
void get_now_weather_data(JsonDocument &doc);
size_t readBytesSize = 0;
// 用来存放解压前的JSON数据
static uint8_t buffer[1280] = {0};
// 用来存放解压后的JSON数据
uint8_t *outbuffer = NULL;
// 获取实时天气数据
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");
}
}
}
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>();
Serial.print("wd.code: ");
Serial.println(wd.code);
Serial.print("wd.updateTime: ");
Serial.println(wd.updateTime);
Serial.print("wd.now_obsTime: ");
Serial.println(wd.now_obsTime);
Serial.print("wd.now_temp: ");
Serial.println(wd.now_temp);
Serial.print("wd.now_feelsLike: ");
Serial.println(wd.now_feelsLike);
Serial.print("wd.now_icon: ");
Serial.println(wd.now_icon);
Serial.print("wd.now_text: ");
Serial.println(wd.now_text);
Serial.print("wd.now_wind360: ");
Serial.println(wd.now_wind360);
Serial.print("wd.now_windDir: ");
Serial.println(wd.now_windDir);
Serial.print("wd.now_windScale: ");
Serial.println(wd.now_windScale);
Serial.print("wd.now_windSpeed: ");
Serial.println(wd.now_windSpeed);
Serial.print("wd.now_humidity: ");
Serial.println(wd.now_humidity);
Serial.print("wd.now_precip: ");
Serial.println(wd.now_precip);
Serial.print("wd.now_pressure: ");
Serial.println(wd.now_pressure);
Serial.print("wd.now_vis: ");
Serial.println(wd.now_vis);
}
五、解压缩JSON数据源代码下载和运行效果展示
百度网盘下载:UZlib_Qweather_CompressedJsonData_7, 提取码:ivfq
友情提示:(1)请务必将 ssid 和 password 修改成您所在环境的名称和密码;(2)请务必const String key 修改成您自己申请的和风天气API密钥。
如果您能在串口监视器看到如下信息,那么恭喜您程序运行成功了。
参考文档
1. JSON 基本使用_json怎么用-CSDN博客
2. 如何获取和风天气Web API的KEY?(简要步骤)_天气预报web api key-CSDN博客
3. JSON——概述、JSON语法、序列化和反序列化_所有文档都可以通过json序列化吗-CSDN博客