【项目1】多功能墨水屏新闻、天气台历(一)

非广告,真心推荐:太极创客

太极创客 – Arduino, ESP8266物联网的应用、开发和学习资料http://www.taichi-maker.com/

项目来源:【开源】教程——4.2寸多功能微雪墨水屏新闻台历gxepd2版本_哔哩哔哩_bilibili


目录

显示效果

esp32

通用驱动板

DHT11

代码部分

GxEPD2_Version_4in2_epd_calendar.ino文件下代码

初始化

主函数

定义部分

Webserver.h关于wifi代码

参数配置

初始化setup()调用的wifi_init()

配网

等待连接

配网模式

setupMode()

对字符串进行URL解码

makePage()

gatData文件关于GetData()函数

CallHttps.ino

JsonWeather.ino和天气相关的数据处理

ParseActualWeather(String content, struct LifeIndex* data)解析生活指数

ParseActualWeather(String content, struct ActualWeather* data)解析实况天气

ParseFutureWeather(String content, struct FutureWeather* data)解析未来天气

JsonHitokoto文件,与一言处理数据相关。

JsonNews文件中新闻数据处理


这个项目看起来不太简单。

ArduinoJson:ArduinoJson: Efficient JSON serialization for embedded C++

GxEPD2:屏幕驱动库https://github.com/jeason-j/GxEPD2GitHub - ZinggJM/GxEPD2: Arduino Display Library for SPI E-Paper Displays

Timezone:Arduino时区库GitHub - JChristensen/Timezone: Arduino library to facilitate time zone conversions and automatic daylight saving (summer) time adjustments.

U8g2:图形库Home · olikraus/u8g2 Wiki · GitHub

U8g2_for_Adafruit_GFX.h:joe/U8g2_for_Adafruit_GFX

Dht11:这个方面库很多,随便搜搜吧。没找到参考文档之类的。

显示效果

 

啧~违规,拉倒吧!GitHub - zhushengji/GxEPD2_Version_4in2_epd_calendar: 基于GXEPD2库文件开发的通用版多功能墨水屏台历

esp32

图片来源:ESP32 GPIO_我不想35岁失业的博客-CSDN博客_esp32 gpio

通用驱动板

说实话,按照作者的说法没找到。但是在立创开源社区有许多大佬画的板子。基本相仿,按照接线规则连接即可。

DHT11

DHT11接到GPIO21

由displayCode文件可知引脚连接到

/*温度*/
dht11 DHT11;
void gettem(){
  DHT11.read(21);
  String temperature=String(DHT11.temperature)+"℃";
  String humidity = String(DHT11.humidity)+"%";
  u8g2Fonts.setFont(chinese_city_gb2312);
  u8g2Fonts.setForegroundColor(GxEPD_BLACK);  // 设置前景色
  u8g2Fonts.setBackgroundColor(GxEPD_WHITE);  // 设置背景色
  u8g2Fonts.setCursor(272,222);
  u8g2Fonts.print("室内");
  display.drawInvertedBitmap(312, 210, wendu, 16, 16, GxEPD_BLACK);//画图
  u8g2Fonts.setCursor(330,222);
  u8g2Fonts.print(temperature);
  display.drawInvertedBitmap(358, 210, shidu, 16, 16, GxEPD_BLACK);//画图
  u8g2Fonts.setCursor(376,222);
  u8g2Fonts.print(humidity);
}

余下还有电池等外围电路与控制无关,不做分析。

代码部分

GxEPD2_Version_4in2_epd_calendar.ino文件下代码

初始化


void setup()
{
  Serial.begin(115200);    //串口波特率
  EEPROM.begin(4096);      //申请size内存
  display.init();          //display初始化
  u8g2Fonts.begin(display);//将u8g2连接到display
  display.firstPage();     //firstPage方法会把当前页码位置变成0
  display.display(1);      //延时
  wifi_init();             //wifi初始化
}

主函数

void loop() {
  /*后台配置*/
  if (settingMode) {        //Webserver.h中定义,配置网络的问题
    dnsServer.processNextRequest();
  }
  webServer.handleClient();
  if (!settingMode) {
    GetData();  //获取Json数据                             //定义在gatData
    initNTP();  //初始化时间                               //定义在NTP.h
    display.fillScreen(GxEPD_WHITE);  //初始化屏幕        
    get_weather();  //获取天气                            //定义在displayCode
    updatetime();   //时间                                //定义在displayCode
    hitokoto();     //一言                                //定义在displayCode
    newsdisplay();  //新闻                                //定义在displayCode
    gettem();       //温湿度                              //定义在displayCode
    udc++;          //计数      //心知天气免费20次每分钟
    display.nextPage();
    /* 1.轻度休眠会关闭WiFi蓝牙以降低功耗,所以唤醒后需要重新联网
     * 2.之所以不用功耗更低的深度休眠,因为深度休眠只保留RTC,内存
     *   中的数据会丢失
     * 3.休眠函数中时间单位是微秒,所以数据要X1000000
    */
    //轻度休眠有概率导致连不上网
    /*esp_sleep_enable_timer_wakeup(300000000);//5分钟刷新一次
    esp_light_sleep_start();
    delay(1000);
    wifi_init();//休眠后需要重新联网*/
    delay(300000);//5分钟更新一次
    }
    /*深度睡眠会导致内存中数据丢失让新闻切换出问题,故弃用
     * esp_sleep_enable_timer_wakeup(20000000);
     * esp_deep_sleep_start();
     */
  }

初始化和主函数看起来都不难理解。

定义部分

定义要统一格式啊!为什么不统一格式!

统一格式增加可读性!!!

#include <U8g2_for_Adafruit_GFX.h>
#include <GxEPD2_3C.h>
#include <ArduinoJson.h>
#include <Timezone.h>
GxEPD2_3C<GxEPD2_420c, GxEPD2_420c::HEIGHT> display(GxEPD2_420c(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // GDEW042Z15
#include "gb2312.c"
#include "imagedata.h"
#include "Webserver.h"
#include "NTP.h"
#include <dht11.h>
extern const uint8_t chinese_city_gb2312[239032]   //这么多,真占内存  U8G2_FONT_SECTION("chinese_city_gb2312");        //国标2312

/* 自己写代码喜欢这样,看别人代码就不喜欢这样 */
U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;

//****** HTTP服务器配置 ******
String language = "zh-Hans"; // 请求语言
String url_yiyan = "https://v1.hitokoto.cn/";//一言获取地址
String url_ActualWeather;   //实况天气地址
String url_FutureWeather;   //未来天气地址


//****** 天气数据
//我们要从此网页中提取的数据的类型
//定义结构体
struct ActualWeather
{
  char status_code[64];  //错误代码
  char city[16];         //城市名称
  char weather_name[16]; //天气现象名称
  char weather_code[4];  //天气现象代码
  char temp[5];          //温度
  char last_update[25];  //最后更新时间
};
ActualWeather actual;  //创建结构体变量 目前的

//未来天气
struct FutureWeather
{
  char status_code[64];       //错误代码

  char date0[14];             //今天日期
  char date0_text_day[20];    //白天天气现象名称
  char date0_code_day[4];     //白天天气现象代码
  char date0_text_night[16];  //晚上天气现象名称
  char date0_code_night[4];   //晚上天气现象代码
  char date0_high[5];         //最高温度
  char date0_low[5];          //最低温度
  char date0_humidity[5];     //相对湿度
  char date0_wind_scale[5];   //风力等级

  char date1[14];             //明天日期
  char date1_text_day[20];    //白天天气现象名称
  char date1_code_day[4];     //白天天气现象代码
  char date1_text_night[16];  //晚上天气现象名称
  char date1_code_night[4];   //晚上天气现象代码
  char date1_high[5];         //最高温度
  char date1_low[5];          //最低温度
  //char date1_humidity[5];     //相对湿度

  char date2[14];             //后天日期
  char date2_text_day[20];    //白天天气现象名称
  char date2_code_day[4];     //白天天气现象代码
  char date2_text_night[16];  //晚上天气现象名称
  char date2_code_night[4];   //晚上天气现象代码
  char date2_high[5];         //最高温度
  char date2_low[5];          //最低温度
  //char date2_humidity[5];     //相对湿度
};
FutureWeather future; //创建结构体变量 未来

struct LifeIndex //生活指数
{
  char status_code[64];  //错误代码
  char uvi[10];          //紫外线指数
}; 
LifeIndex life_index; //创建结构体变量 未来

struct News  //新闻API
{
  char status_code[64]; //错误代码
  char title[11][64];       //作者规划只显示11行
};
News xinwen; //创建结构体变量 新闻

struct Hitokoto  //一言API
{
  char status_code[64]; //错误代码
  char hitokoto[64];
};
Hitokoto yiyan; //创建结构体变量 一言

//****** 一些变量 ******
String webServer_news = " ";
uint8_t client_count = 0;  //连接服务器的超时计数,暂未使用
uint8_t client_error = 0;  //错误代码,暂未使用
boolean night_updated = 1; //夜间更新 1-不更新 0-更新

//RTC临时数据
#define RTC_hour_dz 0           //小时地址
#define RTC_night_count_dz 1    //夜间计数地址
#define RTC_peiwang_state_dz 2  //配网状态地址
uint32_t RTC_hour = 100;        //小时
uint32_t RTC_night_count = 0;   //24-6点,夜间不更新计数
int32_t night_count_max = 0;    //需要跳过几次
uint32_t RTC_peiwang_state = 0; //配网状态 1-需要

//int daydate;//当天日期
int udc=0;//更新次数记录  //在loop函数里++

Webserver.h关于wifi代码

wifi_init()函数在Webserver.h

参数配置

#include <DNSServer.h>
#include <EEPROM.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include "WiFi.h"
const IPAddress apIP(192, 168, 1, 1);
const char* apSSID = "码蜂科技配网WiFi";
boolean settingMode;
String ssidList;
String xz_code;

DNSServer dnsServer;

WebServer webServer(80);
bool flag =false;

初始化setup()调用的wifi_init()

void wifi_init(){
  delay(10);
  if (restoreConfig()) {        //有没有存储过wifi账号密码、心知天气的密钥,没有则启动配网
    if (checkConnection()) {
      settingMode = false;
      startWebServer();
      return;
    }
  }
  settingMode = true;
  setupMode();
}

配网

boolean restoreConfig() {
  Serial.println("Reading EEPROM...");
  String ssid = "";    //字符串
  String pass = "";    
  String authcode= "";
  if (EEPROM.read(4000) != 0) {
    for (int i = 4000; i < 4032; ++i) {
      char ch = EEPROM.read(i);
      if (isalpha(ch) || isdigit(ch)) {    //判断字母||十进制数
        ssid += char(EEPROM.read(i));
      }

    }
    Serial.print("WiFi: ");
    Serial.println(ssid);

    for (int i = 4032; i < 4064; ++i) {
      char ch = EEPROM.read(i);
      if (isalpha(ch) || isdigit(ch)) {
        pass += char(EEPROM.read(i));
      }
    }
    Serial.print("密码: ");
    Serial.println(pass);
    
    //心知天气密钥
    for (int i = 4064; i < 4096; ++i) {
      char ch = EEPROM.read(i);
      if (isalpha(ch) || isdigit(ch)||ch=='-') {
        authcode += char(EEPROM.read(i));
      }
    }
    Serial.print("密钥: ");
    Serial.println(authcode);

    WiFi.begin(ssid.c_str(), pass.c_str());        //c_str()返回当前字符串的首字符地址

    xz_code = authcode.c_str();        //心知天气密钥

    WiFi.begin(ssid.c_str(), pass.c_str());
    return true;
  }
  else {
    Serial.println("Config not found.");
    return false;
  }
}

等待连接

boolean checkConnection() {
  int count = 0;
  Serial.println("正在等待连接");
  while ( count < 30 ) {                //30s
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("成功连接!");
      return (true);
    }
    delay(500);
    Serial.print(".");
    count++;
  }
  Serial.println("连接超时.");
  return false;
}

配网模式

void startWebServer() {
//  display.drawInvertedBitmap(0, 0, jitang, 400, 300, GxEPD_BLACK);//画图
//  Serial.println("开屏动画");
  
  if (settingMode) {
    Serial.print("Starting Web Server at ");
    Serial.println(WiFi.softAPIP());
    webServer.on("/settings", []() {
      //自己生成一个网页
      String s = "<head><meta charset=\"utf-8\"></head><h1>码蜂Wi-Fi配置</h1><p>请在选择WiFi名称后输入对应的WiFi密码.</p>";
      s += "<form method=\"get\" action=\"setap\"><label>网络:</label><select name=\"ssid\">";
      s += ssidList;
      //提交数据
      s += "</select><br>密码:<input name=\"pass\" length=64 type=\"password\"><br>心知密钥:<input name=\"authcode\" length=64 type=\"password\"><br>";
      s += "<input name=\"保存并提交\"  type=\"submit\"></form>";
      webServer.send(200, "text/html", makePage("码蜂Wi-Fi配置", s));    //向客户端发送响应信息。
    });

    webServer.on("/setap", []() {
        //清空数据,防止出现WiFi账号密码长度不一致导致的无法联网问题
      for (int i = 4000; i < 4096; ++i) {
        EEPROM.begin(4096);
        EEPROM.write(i, 0);
        EEPROM.commit();
      }

      String ssid = urlDecode(webServer.arg("ssid"));
      Serial.print("SSID: ");
      Serial.println(ssid);

      String pass = urlDecode(webServer.arg("pass"));
      Serial.print("Password: ");
      Serial.println(pass);

      String authcode = urlDecode(webServer.arg("authcode"));
      Serial.print("authcode: ");
      Serial.println(authcode);

      Serial.println("Writing ssid to EEPROM...");

      for (int i = 0; i < ssid.length(); ++i) {
        EEPROM.begin(4096);
        EEPROM.write(4000 + i, ssid[i]);
        EEPROM.commit();
      }
      Serial.println("Writing Password to EEPROM...");

      for (int i = 0; i < pass.length(); ++i) {
        EEPROM.begin(4096);
        EEPROM.write(4032 + i, pass[i]);
        EEPROM.commit();
      }
      EEPROM.end();        //这两行多余了吧
      Serial.println("Write EEPROM done!");
      Serial.println("Writing authcode to EEPROM...");

      for (int i = 0; i < authcode.length(); ++i) {
        EEPROM.begin(4096);
        EEPROM.write(4064 + i, authcode[i]);
        EEPROM.commit();
      }
      EEPROM.end();
      Serial.println("Write EEPROM done!");

      String s = "<h1>设置结束!</h1><p>设备即将在重启后接入 \"";

      s += ssid;
      s += "\" ";
      webServer.send(200, "text/html", makePage("码蜂Wi-Fi配置", s));

      ESP.restart();        //重启
    });
    webServer.onNotFound([]() {
      String s = "<h1>配网模式</h1><p><a href=\"/settings\">点击配网</a></p>";
      webServer.send(200, "text/html", makePage("配网模式", s));
    });
  }
  else {
    Serial.print("Starting Web Server at ");
    Serial.println(WiFi.localIP());
    webServer.on("/", []() {
      String s = "<h1>STA mode</h1><p><a href=\"/reset\">重置WiFi设置</a></p>";
      webServer.send(200, "text/html", makePage("STA mode", s));
    });
    webServer.on("/reset", []() {
      for (int i = 4000; i < 4096; ++i) {
        EEPROM.begin(4096);
        EEPROM.write(i, 0);
        //        EEPROM.commit();
        EEPROM.end();
      }

      String s = "<h1>Wi-Fi 设置已重置</h1><p>请重启设备.</p>";
      webServer.send(200, "text/html", makePage("Reset Wi-Fi Settings", s));
    });
  }
  webServer.begin();
}

setupMode()

void setupMode() {
  WiFi.mode(WIFI_STA);        //无线终端模式
  WiFi.disconnect();          //断开连接
  delay(100);
  int n = WiFi.scanNetworks();//扫描周围wifi
  delay(100);
  Serial.println("");
  for (int i = 0; i < n; ++i) {
    ssidList += "<option value=\"";
    ssidList += WiFi.SSID(i);        //路由器发送的无线信号的名字
    ssidList += "\">";
    ssidList += WiFi.SSID(i);
    ssidList += "</option>";
  }
  delay(100);

  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));    //配置接入点网络信息:IP地址,网关,子网掩码。
  WiFi.softAP(apSSID);        //用于启动校验式wifi网络或开放式wifi网络
  dnsServer.start(53, "*", apIP);    //启动ESP8266模块的DNS服务

  /*如果联网失败就显示失败提醒*/
  display.fillScreen(GxEPD_WHITE);
  display.drawInvertedBitmap(0, 0, jitang, 400, 300, GxEPD_BLACK);//画图
  display.nextPage();
  startWebServer();
  Serial.print("Starting Access Point at \"");
  Serial.print(apSSID);
  Serial.println("\"");
}

对字符串进行URL解码

String urlDecode(String input) {
  String s = input;
  s.replace("%20", " ");
  s.replace("+", " ");
  s.replace("%21", "!");
  s.replace("%22", "\"");
  s.replace("%23", "#");
  s.replace("%24", "$");
  s.replace("%25", "%");
  s.replace("%26", "&");
  s.replace("%27", "\'");
  s.replace("%28", "(");
  s.replace("%29", ")");
  s.replace("%30", "*");
  s.replace("%31", "+");
  s.replace("%2C", ",");
  s.replace("%2E", ".");
  s.replace("%2F", "/");
  s.replace("%2C", ",");
  s.replace("%3A", ":");
  s.replace("%3A", ";");
  s.replace("%3C", "<");
  s.replace("%3D", "=");
  s.replace("%3E", ">");
  s.replace("%3F", "?");
  s.replace("%40", "@");
  s.replace("%5B", "[");
  s.replace("%5C", "\\");
  s.replace("%5D", "]");
  s.replace("%5E", "^");
  s.replace("%5F", "-");
  s.replace("%60", "`");
  return s;
}

makePage()

String makePage(String title, String contents) {
  String s = "<!DOCTYPE html><html><head>";
  s += "<meta name=\"viewport\" content=\"width=device-width,user-scalable=0\">";
  s += "<title>";
  s += title;
  s += "</title></head><body>";
  s += contents;
  s += "</body></html>";
  return s;
}

下面从loop函数开始分析操作流程

首先是GetData()函数,获取Json数据,定义在gatData文件中,整个gatData文件也只有这一个函数。

gatData文件关于GetData()函数

定义在GxEPD2_Version_4in2_epd_calendar.ino文件下

String url_ActualWeather;   //实况天气地址
String url_FutureWeather;   //未来天气地址

 心知天气、一言、新闻等数据接口。

void GetData()
{
  //"http://api.seniverse.com/v3/weather/now.json?key=S6pG_Q54kjfnBAi6i&location=深圳&language=zh-Hans&unit=c"
  //拼装实况天气API地址
  url_ActualWeather = "https://api.seniverse.com/v3/weather/now.json";
  url_ActualWeather += "?key=" + xz_code;
  url_ActualWeather += "&location=ip" ;
  url_ActualWeather += "&language=" + language;
  url_ActualWeather += "&unit=c";
  
  //https://api.seniverse.com/v3/weather/daily.json?key=S6pG_Q54kjfnBAi6i&location=深圳&language=zh-Hans&unit=c&start=0&days=3
  //拼装实况未来API地址
  url_FutureWeather = "https://api.seniverse.com/v3/weather/daily.json";
  url_FutureWeather += "?key=" + xz_code;
  url_FutureWeather += "&location=ip";
  url_FutureWeather += "&language=" + language;
  url_FutureWeather += "&unit=c";
  url_FutureWeather += "&start=0";
  url_FutureWeather += "&days=3";

  //https://api.seniverse.com/v3/life/suggestion.json?key=S6pG_Q54kjfnBAi6i&location=shanghai&language=zh-Hans
  //拼装生活指数
  //在GxEPD2_Version_4in2_epd_calendar.ino没定义跑在这里定义了
  String url_LifeIndex = "https://api.seniverse.com/v3/life/suggestion.json";
  url_LifeIndex += "?key=" + xz_code;
  url_LifeIndex += "&location=ip";

  //新闻地址
  //就这俩在GxEPD2_Version_4in2_epd_calendar.ino中定义多好啊
  String url_News="";
  if(udc%3==0){
    url_News="https://api.vvhan.com/api/wbhot";//微博
  }else if(udc%3==1){
    url_News = "https://api.iyk0.com/ysxw/";//央视新闻
  }else{
    url_News = "http://api.rosysun.cn/zhihu/";    //知乎的不能用了
  }
//  String url_News = "https://api.iyk0.com/ysxw/";//央视新闻
//  String url_News = "http://api.rosysun.cn/zhihu/";
//  String url_News = "https://api.iyk0.com/bdr";//百度头条
//  String url_News = "https://api.vvhan.com/api/douban";
  //请求数据并Json处理
//  display_partialLine(7, "获取生活指数");
/*两个半小时更新一次天气,节省访问次数且不影响使用*/

    //第二个参数为结构体!! //callHttps定义在CallHttps
  if(udc%30==0){                                                //函数定义在JsonWeather
    ParseActualWeather(callHttps(url_LifeIndex), &life_index); //获取生活指数
  
    ParseActualWeather(callHttps(url_ActualWeather), &actual);

//  display_bitmap_bottom(Bitmap_wlq2, "获取未来天气数据中");
    ParseFutureWeather(callHttps(url_FutureWeather), &future);
  }
  

  ParseHitokoto(callHttps(url_yiyan), &yiyan);        //函数定义在JsonHitokoto

  ParseNews(callHttps(url_News),&xinwen);        //函数定义在JsonNews
}

既然使用了callHttps()函数,那就先看CallHttps.ino

CallHttps.ino

仅有一个函数定义于此文件下。

String callHttps(String url)
{
  String payload;
  Serial.print("requesting URL: ");
  Serial.println(url);

  //###***---+++===~~~!!!$$$^^^&&&
  HTTPClient https;

  if (https.begin(url))    //通过HTTP协议向网络服务器发送HTTP请求
  {
    int httpsCode = https.GET();    //从指定的资源请求数据,返回服务器状态码
    if (httpsCode > 0)  //判断有无返回值
    {
      /*payload = https.getString();
      Serial.println(payload);
      return payload;*/
      if (httpsCode == 200 || httpsCode == 304 || httpsCode == 403 || httpsCode == 404 || httpsCode ==   500) //判断请求是正确
      {
        payload = https.getString();    //可用于获取服务器响应中的响应体信息。响应体信息将以字符串的形式进行返回。
//        Serial.println(payload);
        Serial.println(" ");
        return payload;
      }
      else
      {
        Serial.print("请求错误:"); Serial.println(httpsCode); Serial.println(" ");
        char* httpsCode_c = new char[8];
        itoa(httpsCode, httpsCode_c, 10); //int转char*   10是进制
        payload = "{\"status_code\":\"" + String("请求错误:") + String(httpsCode_c) + "\"}";
        return payload;
      }
    }
    else
    {
      Serial.println(" "); Serial.print("GET请求错误:"); Serial.println(httpsCode);
      Serial.printf("[HTTPS] GET... 失败, 错误: %s\n", https.errorToString(httpsCode).c_str());
      payload = "{\"status_code\":\"" + String(https.errorToString(httpsCode).c_str()) + "\"}";
      //Serial.println(payload);
      return payload;
    }
  }
  else
  {
    Serial.printf("[HTTPS] 无法连接服务器\n");
    payload = "{\"status_code\":\"" + String("无法连接服务器") + "\"}";
    return payload;
  }
  https.end();
}

通过类似ParseActualWeather(callHttps(url_LifeIndex), &life_index); 处理的读取函数,所以下面看ParseActualWeather()函数,定义在JsonWeather中!

JsonWeather.ino和天气相关的数据处理

ParseActualWeather(String content, struct LifeIndex* data)解析生活指数

//使用Json解析天气数据,天气实况
bool ParseActualWeather(String content, struct LifeIndex* data)
{
  DynamicJsonDocument json(1536); //分配内存,动态        建立了DynamicJsonDocument对象,该对象名称为json
  DeserializationError error = deserializeJson(json, content); //解析json
  //serializeJson(json, Serial);//构造序列化json,将内容从串口输出
  if (error)
  {
    Serial.print("天气指数加载json配置失败:");
    Serial.println(error.c_str());
    Serial.println(" ");
    String z = "天气指数json配置失败:" + String(error.c_str()) + " " + content;
    //display_partialLine(7, z);
//    esp_sleep(0);

    return false;
  }

  //检查API是否有返回错误信息,有返回则进入休眠
  if (json["status_code"].isNull() == 0) //检查到不为空
  {
    strcpy(data->status_code, json["status_code"]);
    String z;
    if (String(actual.status_code) == "AP010001") z = "API 请求参数错误" ;
    else if (String(actual.status_code) == "AP010002") z = "没有权限访问这个 API 接口" ;
    else if (String(actual.status_code) == "AP010003") z = "API 密钥 key 错误" ;
    else if (String(actual.status_code) == "AP010004") z = "签名错误" ;
    else if (String(actual.status_code) == "AP010005") z = "你请求的 API 不存在" ;
    else if (String(actual.status_code) == "AP010006") z = "没有权限访问这个地点: ";
    else if (String(actual.status_code) == "AP010007") z = "JSONP 请求需要使用签名验证方式" ;
    else if (String(actual.status_code) == "AP010008") z = "没有绑定域名" ;
    else if (String(actual.status_code) == "AP010009") z = "API 请求的 user-agent 与你设置的不一致" ;
    else if (String(actual.status_code) == "AP010010") z = "没有这个地点" ;
    else if (String(actual.status_code) == "AP010011") z = "无法查找到指定 IP 地址对应的城市" ;
    else if (String(actual.status_code) == "AP010012") z = "你的服务已经过期" ;
    else if (String(actual.status_code) == "AP010013") z = "访问量余额不足" ;
    else if (String(actual.status_code) == "AP010014") z = "访问频率超过限制" ;
    else if (String(actual.status_code) == "AP010015") z = "暂不支持该城市的车辆限行信息" ;
    else if (String(actual.status_code) == "AP010016") z = "暂不支持该城市的潮汐数据" ;
    else if (String(actual.status_code) == "AP010017") z = "请求的坐标超出支持的范围" ;
    else if (String(actual.status_code) == "AP100001") z = "心知系统内部错误:数据缺失" ;
    else if (String(actual.status_code) == "AP100002") z = "心知系统内部错误:数据错误" ;
    else if (String(actual.status_code) == "AP100003") z = "心知系统内部错误:服务内部错误" ;
    else if (String(actual.status_code) == "AP100004") z = "心知系统内部错误:网关错误" ;
    else z = "天气指数异常:" + String(actual.status_code);
    //display_partialLine(7, z);
    Serial.print("天气指数异常:"); Serial.println(actual.status_code);
    //esp_sleep(60);
  }

  // 复制我们感兴趣的字符串 ,先检查是否为空,空会导致系统无限重启
  // isNull()检查是否为空 空返回1 非空0
  if (json["results"][0]["suggestion"]["uv"]["brief"].isNull() == 0)          //紫外线指数
    strcpy(data->uvi, json["results"][0]["suggestion"]["uv"]["brief"]);

  //if (json["results"][0]["now"]["text"].isNull() == 0)
  //strcpy(data->weather_name, json["results"][0]["now"]["text"]);

  // 这不是强制复制,你可以使用指针,因为他们是指向"内容"缓冲区内,所以你需要确保
  // 当你读取字符串时它仍在内存中
  return true;
}

ParseActualWeather(String content, struct ActualWeather* data)解析实况天气

都差不多啦!~

//使用Json解析天气数据,天气实况
bool ParseActualWeather(String content, struct ActualWeather* data)
{
  DynamicJsonDocument json(1536); //分配内存,动态
  DeserializationError error = deserializeJson(json, content); //解析json
  //serializeJson(json, Serial);//构造序列化json,将内容从串口输出
  if (error)
  {
    Serial.print("实况天气加载json配置失败:");
    Serial.println(error.c_str());
    Serial.println(" ");
    String z = "实况天气json配置失败:" + String(error.c_str()) + " " + content;
    //display_partialLine(7, z);
//    esp_sleep(0);
    return false;
  }

  //检查API是否有返回错误信息,有返回则进入休眠
  if (json["status_code"].isNull() == 0) //检查到不为空
  {
    strcpy(data->status_code, json["status_code"]);
    String z;
    if (String(actual.status_code) == "AP010001") z = "API 请求参数错误" ;
    else if (String(actual.status_code) == "AP010002") z = "没有权限访问这个 API 接口" ;
    else if (String(actual.status_code) == "AP010003") z = "API 密钥 key 错误" ;
    else if (String(actual.status_code) == "AP010004") z = "签名错误" ;
    else if (String(actual.status_code) == "AP010005") z = "你请求的 API 不存在" ;
    else if (String(actual.status_code) == "AP010006") z = "没有权限访问这个地点" ;
    else if (String(actual.status_code) == "AP010007") z = "JSONP 请求需要使用签名验证方式" ;
    else if (String(actual.status_code) == "AP010008") z = "没有绑定域名" ;
    else if (String(actual.status_code) == "AP010009") z = "API 请求的 user-agent 与你设置的不一致" ;
    else if (String(actual.status_code) == "AP010010") z = "没有这个地点";
    else if (String(actual.status_code) == "AP010011") z = "无法查找到指定 IP 地址对应的城市" ;
    else if (String(actual.status_code) == "AP010012") z = "你的服务已经过期" ;
    else if (String(actual.status_code) == "AP010013") z = "访问量余额不足" ;
    else if (String(actual.status_code) == "AP010014") z = "访问频率超过限制" ;
    else if (String(actual.status_code) == "AP010015") z = "暂不支持该城市的车辆限行信息" ;
    else if (String(actual.status_code) == "AP010016") z = "暂不支持该城市的潮汐数据" ;
    else if (String(actual.status_code) == "AP010017") z = "请求的坐标超出支持的范围" ;
    else if (String(actual.status_code) == "AP100001") z = "心知系统内部错误:数据缺失" ;
    else if (String(actual.status_code) == "AP100002") z = "心知系统内部错误:数据错误" ;
    else if (String(actual.status_code) == "AP100003") z = "心知系统内部错误:服务内部错误" ;
    else if (String(actual.status_code) == "AP100004") z = "心知系统内部错误:网关错误" ;
    else z = "实况天气异常:" + String(actual.status_code);
    //display_partialLine(7, z);
    Serial.print("实况天气异常:"); Serial.println(actual.status_code);
    //esp_sleep(60);
  }

  // 复制我们感兴趣的字符串 ,先检查是否为空,空会导致系统无限重启
  // isNull()检查是否为空 空返回1 非空0
  if (json["results"][0]["location"]["name"].isNull() == 0)
    strcpy(data->city, json["results"][0]["location"]["name"]);
  if (json["results"][0]["now"]["text"].isNull() == 0)
    strcpy(data->weather_name, json["results"][0]["now"]["text"]);
  if (json["results"][0]["now"]["code"].isNull() == 0)
    strcpy(data->weather_code, json["results"][0]["now"]["code"]);
  if (json["results"][0]["now"]["temperature"].isNull() == 0)
    strcpy(data->temp, json["results"][0]["now"]["temperature"]);
  if (json["results"][0]["last_update"].isNull() == 0)
    strcpy(data->last_update, json["results"][0]["last_update"]);
  // 这不是强制复制,你可以使用指针,因为他们是指向"内容"缓冲区内,所以你需要确保
  // 当你读取字符串时它仍在内存中
  
  return true;
}

ParseFutureWeather(String content, struct FutureWeather* data)解析未来天气

//使用Json解析天气数据,今天和未来2天
bool ParseFutureWeather(String content, struct FutureWeather* data)
{
  DynamicJsonDocument json(2560); //分配内存,动态
  DeserializationError error = deserializeJson(json, content); //解析json
  // serializeJson(json, Serial);//构造序列化json,将内容从串口输出
  if (error)
  {
    Serial.print("未来天气json配置失败:");
    Serial.println(error.c_str());
    Serial.println(" ");
    String z = "未来天气加载json配置失败:" + String(error.c_str()) + " " + content;
    //display_partialLine(7, z);
//    esp_sleep(0);
    return false;
  }

  //检查API是否有返回错误信息,有返回则进入休眠
  if (json["status_code"].isNull() == 0) //检查到不为空
  {
    strcpy(data->status_code, json["status_code"]);
    String z;
    if (String(future.status_code) == "AP010001") z = "API 请求参数错误" ;
    else if (String(future.status_code) == "AP010002") z = "没有权限访问这个 API 接口" ;
    else if (String(future.status_code) == "AP010003") z = "API 密钥 key 错误" ;
    else if (String(future.status_code) == "AP010004") z = "签名错误" ;
    else if (String(future.status_code) == "AP010005") z = "你请求的 API 不存在" ;
    else if (String(future.status_code) == "AP010006") z = "没有权限访问这个地点: ";
    else if (String(future.status_code) == "AP010007") z = "JSONP 请求需要使用签名验证方式" ;
    else if (String(future.status_code) == "AP010008") z = "没有绑定域名" ;
    else if (String(future.status_code) == "AP010009") z = "API 请求的 user-agent 与你设置的不一致" ;
    else if (String(future.status_code) == "AP010010") z = "没有这个地点" ;
    else if (String(future.status_code) == "AP010011") z = "无法查找到指定 IP 地址对应的城市" ;
    else if (String(future.status_code) == "AP010012") z = "你的服务已经过期" ;
    else if (String(future.status_code) == "AP010013") z = "访问量余额不足" ;
    else if (String(future.status_code) == "AP010014") z = "访问频率超过限制" ;
    else if (String(future.status_code) == "AP010015") z = "暂不支持该城市的车辆限行信息" ;
    else if (String(future.status_code) == "AP010016") z = "暂不支持该城市的潮汐数据" ;
    else if (String(future.status_code) == "AP010017") z = "请求的坐标超出支持的范围" ;
    else if (String(future.status_code) == "AP100001") z = "心知系统内部错误:数据缺失" ;
    else if (String(future.status_code) == "AP100002") z = "心知系统内部错误:数据错误" ;
    else if (String(future.status_code) == "AP100003") z = "心知系统内部错误:服务内部错误" ;
    else if (String(future.status_code) == "AP100004") z = "心知系统内部错误:网关错误" ;
    else z = "未来天气异常:" + String(future.status_code);
    //display_partialLine(7, z);
    Serial.print("未来天气异常:"); Serial.println(future.status_code);
    //esp_sleep(60);
  }

  // 复制我们感兴趣的字符串,先检查是否为空,空会复制失败导致系统无限重启
  if (json["results"][0]["daily"][0]["date"].isNull() == 0)        //日期
    strcpy(data->date0, json["results"][0]["daily"][0]["date"]);
  if (json["results"][0]["daily"][1]["date"].isNull() == 0)
    strcpy(data->date1, json["results"][0]["daily"][1]["date"]);
  if (json["results"][0]["daily"][2]["date"].isNull() == 0)
    strcpy(data->date2, json["results"][0]["daily"][2]["date"]);

  if (json["results"][0]["daily"][0]["text_day"].isNull() == 0)    //白天天气现象
    strcpy(data->date0_text_day, json["results"][0]["daily"][0]["text_day"]);
  if (json["results"][0]["daily"][1]["text_day"].isNull() == 0)
    strcpy(data->date1_text_day, json["results"][0]["daily"][1]["text_day"]);
  if (json["results"][0]["daily"][2]["text_day"].isNull() == 0)
    strcpy(data->date2_text_day, json["results"][0]["daily"][2]["text_day"]);

  if (json["results"][0]["daily"][0]["text_night"].isNull() == 0)    //晚间天气现象
    strcpy(data->date0_text_night, json["results"][0]["daily"][0]["text_night"]);
  if (json["results"][0]["daily"][1]["text_night"].isNull() == 0)
    strcpy(data->date1_text_night, json["results"][0]["daily"][1]["text_night"]);
  if (json["results"][0]["daily"][2]["text_night"].isNull() == 0)
    strcpy(data->date2_text_night, json["results"][0]["daily"][2]["text_night"]);

  if (json["results"][0]["daily"][0]["high"].isNull() == 0)
    strcpy(data->date0_high, json["results"][0]["daily"][0]["high"]);  //最高温度
  if (json["results"][0]["daily"][1]["high"].isNull() == 0)
    strcpy(data->date1_high, json["results"][0]["daily"][1]["high"]);
  if (json["results"][0]["daily"][2]["high"].isNull() == 0)
    strcpy(data->date2_high, json["results"][0]["daily"][2]["high"]);

  if (json["results"][0]["daily"][0]["low"].isNull() == 0)             //最低温度
    strcpy(data->date0_low, json["results"][0]["daily"][0]["low"]);
  if (json["results"][0]["daily"][1]["low"].isNull() == 0)
    strcpy(data->date1_low, json["results"][0]["daily"][1]["low"]);
  if (json["results"][0]["daily"][2]["low"].isNull() == 0)
    strcpy(data->date2_low, json["results"][0]["daily"][2]["low"]);

  if (json["results"][0]["daily"][0]["humidity"].isNull() == 0)                //湿度
    strcpy(data->date0_humidity, json["results"][0]["daily"][0]["humidity"]);

  if (json["results"][0]["daily"][0]["wind_scale"].isNull() == 0)        //风力等级
    strcpy(data->date0_wind_scale, json["results"][0]["daily"][0]["wind_scale"]);
  // 这不是强制复制,你可以使用指针,因为他们是指向"内容"缓冲区内,所以你需要确保
  // 当你读取字符串时它仍在内存中
  return true;
}

读读到的Json数据,然后存入到结构体中。没啥难度。

ParseHitokoto(callHttps(url_yiyan), &yiyan);获取一言数据,在JsonHitokoto文件中定义。

JsonHitokoto文件,与一言处理数据相关。

String url_yiyan = "https://v1.hitokoto.cn/";//一言获取地址
//****** 获取一言数据
void ParseHitokoto(String content, struct Hitokoto* data)
{
  DynamicJsonDocument json(1536); //分配内存,动态
  DeserializationError error = deserializeJson(json, content); //解析json
  //serializeJson(json, Serial);//构造序列化json,将内容从串口输出
  if (error)   //检查API是否有返回错误信息,有返回则进入休眠
  {
    Serial.print("一言加载json配置失败:");
    Serial.println(error.c_str());
    Serial.println(" ");
    String z = "一言json配置失败:" + String(error.c_str()) + " " + content;
  }
  if (json["status_code"].isNull() == 0) //检查到不为空
  {
    strcpy(data->status_code, json["status_code"]);
    String z = "一言异常:" + String(yiyan.status_code);
    Serial.print("一言异常:"); Serial.println(yiyan.status_code);
  }
  else
  {
    if (json["hitokoto"].isNull() == 0){
      strcpy(data->hitokoto, json["hitokoto"]);
      } 
    else strcpy(data->hitokoto, "哎呀\"hitokoto\"没有数据呢");
  }
  // 复制我们感兴趣的字符串 ,先检查是否为空,空会导致系统无限重启
  // 这不是强制复制,你可以使用指针,因为他们是指向"内容"缓冲区内
  // 所以你需要确保 当你读取字符串时它仍在内存中
  // isNull()检查是否为空 空返回1 非空0
}

比较简单啦~

接下来就是新闻。琪琪:接下来~        老秦:接下来~

ParseNews(String content, struct News* data)

JsonNews文件中新闻数据处理

//****** 获取新闻数据
void ParseNews(String content, struct News* data)
{
  DynamicJsonDocument json(8536); //分配内存,动态 1536
  DeserializationError error = deserializeJson(json, content); //解析json
  //serializeJson(json, Serial);//构造序列化json,将内容从串口输出
  if (error)   //检查API是否有返回错误信息,有返回则进入休眠
  {
    Serial.print("新闻加载json配置失败:");
    Serial.println(error.c_str());
    Serial.println(" ");
    String z = "新闻json配置失败:" + String(error.c_str()) + " " + content;
  }
  if (json["status_code"].isNull() == 0) //检查到不为空
  {
    strcpy(data->status_code, json["status_code"]);
    String z = "新闻异常:" + String(xinwen.status_code);
    Serial.print("新闻异常:"); Serial.println(xinwen.status_code);
  }
  else
  {
    if (json["data"].isNull() == 0){
      for(int i = 0;i<11;i++){
        strcpy(data->title[i], json["data"][i]["title"]);    //取前11个
      }
      
      } 
  }
}

这么多,原来才只看到loop函数里的第一步,啊~ 明天再看吧! 不慌!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值