【项目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函数里的第一步,啊~ 明天再看吧! 不慌!

  • 6
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当然可以帮你设计一款墨水屏驱动板!墨水屏驱动板是用来控制墨水屏幕显示内容的主要电路板,通常包括处理器、存储器、接口电路以及电源管理等部分。 首先,我们需要确定你所使用的墨水屏型号和规格,因为不同的墨水屏可能有不同的驱动要求。一旦确定了具体的型号和规格,我们就可以开始设计驱动板。 以下是一个简单的墨水屏驱动板设计步骤的概述: 1. 确定处理器和存储器:选择适合你应用需求的处理器和存储器。处理器应该具备足够的处理能力来处理图像数据,并且能够与墨水屏接口进行通信。存储器用于存储图像数据和其他必要的程序。 2. 设计接口电路:根据墨水屏的接口要求,设计相应的接口电路。这可能涉及到模拟信号转换、数字信号处理、时序控制等方面的设计。 3. 电源管理:墨水屏通常需要较低的供电电压,并且在显示状态时需要较低的功耗。因此,在设计电源管理电路时,需要考虑如何提供稳定的低压供电,并在不使用时降低功耗。 4. PCB设计:根据以上设计要求,进行PCB布局和布线。注意保证信号完整性和电磁兼容性,以及合理的散热设计。 5. 驱动软件开发:根据你的应用需求,编写驱动软件,实现与墨水屏的通信和显示控制。 以上是一个大致的设计流程,具体的设计细节和步骤可能会因具体的墨水屏型号和应用需求而有所不同。如果你能提供更多的详细信息,我可以给你更具体的建议和指导。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值