非广告,真心推荐:太极创客
太极创客 – Arduino, ESP8266物联网的应用、开发和学习资料http://www.taichi-maker.com/
项目来源:【开源】教程——4.2寸多功能微雪墨水屏新闻台历gxepd2版本_哔哩哔哩_bilibili
目录
GxEPD2_Version_4in2_epd_calendar.ino文件下代码
ParseActualWeather(String content, struct LifeIndex* data)解析生活指数
ParseActualWeather(String content, struct ActualWeather* data)解析实况天气
ParseFutureWeather(String content, struct FutureWeather* data)解析未来天气
这个项目看起来不太简单。
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函数里的第一步,啊~ 明天再看吧! 不慌!