获取时间
概述
ESP8266内置芯片无法完成实时功能,或许可以通过串口把当前时间输入,然后用定时器得到对应的时间,但是定时器不会很准,所以用到一定时间就得校准,特别麻烦。而如果一直获取时间,带宽消耗太大,而且费电,所以我给出的方案是每隔一段时间获取一次进行校准,获取后启动定时器来实现实时的时间更新。
网络API接口
这里我推荐使用苏宁易购的免费API:http://quan.suning.com/getSysTime.do
这个API好用在它返回的是具体的北京时间,而不是时间戳不用自己写解析,但是因为免费,所以用的人很多,有时候会返回403,所以多返回错误多请求几次就好了(用代码逻辑实现)。
信息获取
获取时间和获取天气本质上是一样的,用http访问接口,然后通过ArduinoJSON读取出对应的数据即可。
部分代码段:
bool getTimeInfo()
{
if ((WiFiMulti.run() == WL_CONNECTED)) {
WiFiClient client;
HTTPClient http;
Serial.print("[HTTP] begin...\n");
if (http.begin(client, "http://quan.suning.com/getSysTime.do")) { // HTTP
Serial.print("[HTTP] GET...\n");
// start connection and send HTTP header
int httpCode = http.GET();
// httpCode will be negative on error
if (httpCode > 0) {
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String res = http.getString();
Serial.println(res);
DynamicJsonDocument doc(256);
deserializeJson(doc,res);
String timeData = doc["sysTime2"].as<String>();
Serial.println(timeData);
setCurrentTime(timeData);
http.end();
return true;
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
} else {
Serial.printf("[HTTP} Unable to connect\n");
}
}
else
{
Serial.println("failed to connect to wifi!");
}
http.end();
return false;
}
这个是处理403的片段:
bool gotTime = false;
while(!gotTime)
{
gotTime = getTimeInfo();
}
信息处理
自己写个结构体是最方便的了
struct TimeData
{
int year;
int month;
int date;
int hour;
int minute;
int second;
}currentTime;
void setCurrentTime(String timeData)
{
String year = timeData.substring(0,4);
String month = timeData.substring(5,7);
String date = timeData.substring(8,10);
String hour = timeData.substring(11,13);
String minute = timeData.substring(14,16);
String second = timeData.substring(17,19);
currentTime.year = year.toInt();
currentTime.month = month.toInt();
currentTime.date = date.toInt();
currentTime.hour = hour.toInt();
currentTime.minute = minute.toInt();
currentTime.second = second.toInt();
}
不知道我为什么要把数据定义成int形式,现在懒得改了,其实没必要怎么做,直接定义String形式,因为OLED要求的参数也是String
所以大体思路就是获取数据,设置到全局变量中,接下来打印在OLED屏幕即可
定时器
首先需要一个Ticker对象和回调函数
#include <Ticker.h>
Ticker myTicker;
void ticker_callback();
在获取到第一个时间数据(在setup函数里面)后启动定时器,每秒调用一次callback函数(这里定时器的用法很显而易见,还没了解过的朋友可以上网查一查),在回调函数里面把currentTime.second加1,然后刷新屏幕即可:
//启动定时器:
myTicker.attach(1,ticker_callback);
void ticker_callback()
{
currentTime.second++;
Serial.println(currentTime.second);
showOLED();
}
这里还好,但是如果你想这么做,那程序肯定崩!
更新时间和一个重要的问题
按上面那样说,在一段时间后自动重新获取时间,那么我们初定这段时间为一分钟吧,然后一分钟到了自动刷新时间,就有如下代码:
void ticker_callback()
{
currentTime.second++;
Serial.println(currentTime.second);
if(currentTime.second >= 60)
{
myTicker.detach();
bool gotTime = false;
while(!gotTime)
{
gotTime = getTimeInfo();
}
if(gotTime)
{
myTicker.attach(1,ticker_callback);
}
}
}
如果你真的这么写,那就肯定崩了,因为定时器回调函数不能太复杂,这是特别需要注意的点!
所以应该把更新操作和定时器回调函数分开,怎么做到呢,我想了一种办法:
bool needTo_UpdateTime;
void ticker_callback()
{
Serial.println(currentTime.second);
currentTime.second++;
if(currentTime.second >= 60)
{
myTicker.detach();
needTo_UpdateTime = true;
}
}
void loop() {
// put your main code here, to run repeatedly:
if(needTo_UpdateTime)
{
bool gotTime = false;
while(!gotTime)
{
gotTime = getTimeInfo();
}
if(gotTime)
{
myTicker.attach(1,ticker_callback);
}
needTo_UpdateTime = false;
}
}
在loop中判断是我认为最可靠的方式了!
另外,OLED屏幕的显示也稍微有些复杂,建议加个needTo_UpdateOLED变量来确保不会出现大bug