【NodeMCU实时天气时钟温湿度项目 7】和风天气API返回JSON数据信息的解压缩实现——ArduinoUZlib功能库

        今天是第七专题,主要内容是:导入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 压缩文件。

        (2)解压 zip 文件,生 成ArduinoUZlib-main 文件夹。
        (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博客

  • 14
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单片机综合课设中,温湿度采集功能的电子时钟pic16f877a中的电源电路设计非常关键。电源电路的设计应充分考虑到Pic单片机工作的稳定性、电路的安全性以及供电的可靠性。 首先,电源电路中应包含直流稳压电源模块,该模块能够将交流电源(如220V)转换为稳定的直流电压,以满足pic单片机的工作要求。常见的直流稳压电源模块有线性稳压器和开关稳压器两种。线性稳压器简单、成本低廉,但效率相对较低,功耗较大;而开关稳压器则具有高效、高功率转换能力,适合一些对电源要求较高的应用。根据实际情况选择合适的直流稳压电源模块。 此外,电源电路还应包含滤波电路,用于滤除电源中的噪声和杂散干扰,保证单片机正常工作。常见的滤波电路有电源滤波电容和电源滤波电感等。电源滤波电容能有效吸收高频噪声干扰,电源滤波电感则能吸收低频脉冲干扰,通过这两者的组合使用,可以有效净化电源。 另外,为了保护电子时钟电路不受到过流、过载和短路等问题的影响,可以在电源电路中加入保护电路,如过流保护电路、过压保护电路和过载保护电路等。保护电路能够有效避免意外情况对电路的损害,提高电路的可靠性和稳定性。 总之,单片机综合课设温湿度采集功能的电子时钟pic16f877a中的电源电路设计应综合考虑电路的稳定性、安全性和可靠性。通过选择合适的直流稳压电源模块、滤波电路和保护电路等组件,可以有效提高电子时钟的工作效果和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值