NodeMCU(ESP8266)获取NTP时间
很久没有搞ESP8266了,可能是这两年工作太忙了,又或者是对生活失去了斗志,所以最近又重新把两年前的东西重新收拾收拾。
NTP协议
我之前有写一篇NTP 入门介绍,大家如果有对NTP不了解的,可以先查阅这篇《NTP 入门介绍》
为啥要同步时间
之前看到有一个用户通过esp8266做了一个时钟出来( ESP8266物联网创意点阵时钟,女朋友看了都想要!
),自己也想搞一个类似的,然后就发现他有一个功能就是网络自动校准时间,才了解到有NTP这个协议的存在,所以就找到一些代码研究了一番,所以就有了今天这篇博文。
实现思路
esp8266感觉是一个很简单的东西,网上有很多的代码示例,这里我更推荐使用官方的示例库。
如何从官方的示例库中找到我们要用的示例代码:
- net-client-demo
-
Ticker 图片
ticker的使用请参考从零开始的ESP8266探索(11)-定时任务调度器Ticker使用演示
代码
根据上述的示例和其他用户分享的库文件使用方法,我们稍微整理一下,把代码改成我们想要的样子。
- 先连接wifi
- 判断时间是否是正确的,如果不正确就去同步时间
- 如果时间正确就每秒中增加我们的时钟信息,顺便打印出来
上代码
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <Ticker.h>
#ifndef STASSID
#define STASSID "你的WiFi名称"
#define STAPSK "你的WiFi密码"
#endif
const char * ssid = STASSID;
const char * pass = STAPSK;
// 定义时分秒
unsigned int h = 99, m = 99, s = 99;
// 监听本地UDP数据包端口
unsigned int localPort = 2390;
// NTP服务器IP地址
IPAddress timeServerIP;
// NTP服务器网址
const char* ntpServerName = "time.windows.com";
// NTP数据包数据长度
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[ NTP_PACKET_SIZE];
WiFiUDP udp;
// 创建一个需要定时调度的对象
Ticker ticker;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.print("连接wifi中 ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi已连接");
Serial.println("设备IP地址: ");
Serial.println(WiFi.localIP());
Serial.println("开启UDP通信");
udp.begin(localPort);
Serial.print("本地端口为: ");
Serial.println(udp.localPort());
}
void loop() {
//get a random server from the pool
WiFi.hostByName(ntpServerName, timeServerIP);
if (h == 99 || m == 99 || s == 99) {
sendNTPpacket(timeServerIP); // send an NTP packet to a time server
// 等一秒后获取结果
delay(1000);
setTimes();
// 设置定时累加时间操作
ticker.attach(1, addtime);
}
}
/**
累加时间
*/
void addtime() {
if (s == 59) {
s = 0;
if (m == 59) {
m = 0;
if (h == 23) {
h = 0;
} else {
h++;
}
} else {
m++;
}
} else {
s++;
}
Serial.print("当前时间为: ");
Serial.print(h);
Serial.print(":");
Serial.print(m);
Serial.print(":");
Serial.println(s);
}
/**
获取十分秒信息
*/
void setTimes() {
//解析Udp数据包
int cb = udp.parsePacket();
if (!cb) {
//解析包为空
Serial.println("没有接收到任何的数据包!");
} else {
//解析包不为空
Serial.print("接收到的数据包的长度为: ");
Serial.println(cb);
// 解析UDP数据包中的数据
udp.read(packetBuffer, NTP_PACKET_SIZE);
// 说明 todo这里获取到的时间其实不是真实的时间,实际上还包含了网络延时的,但是为了方便,这里我们忽略这个因素的存在
// 取出t2时间的高位和低位数据拼凑成以秒为单位的时间戳
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// 拼凑成以秒为单位的时间戳(时间戳的记录以秒的形式从 1900-01-01 00:00:00 算起)
unsigned long secsSince1900 = highWord << 16 | lowWord;
Serial.print("1900年格式标准的时间戳为:");
Serial.println(secsSince1900);
// 前面的32bit是时间戳的秒数(是用1900-01-01 00:00:00开始的秒数,但是我们的是1970年,所以需要减掉2208988800秒)
const unsigned long seventyYears = 2208988800UL;
unsigned long epoch = secsSince1900 - seventyYears;
Serial.print("1970年格式标准的时间戳为:");
Serial.println(epoch);
// 这里加8 是因为时区的问题,如果不加8,得到的结果就会是其他时区的时间
h = (epoch % 86400L) / 3600 + 8;
m = (epoch % 3600) / 60;
s = epoch % 60;
}
}
/**
发送ntp协议数据包
*/
void sendNTPpacket(IPAddress& address) {
Serial.println("发送ntp数据包...");
// 将字节数组的数据全部设置为0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// 请求部分其实是有很多数据的,具体的请看参考请求报文说明,这里我们就只设置一个请求头部分即可
packetBuffer[0] = 0b11100011;
// 配置远端ip地址和端口
udp.beginPacket(address, 123);
// 把数据写入发送缓冲区
udp.write(packetBuffer, NTP_PACKET_SIZE);
// 发送数据
udp.endPacket();
}
效果图
参考文章
ESP8266物联网创意点阵时钟,女朋友看了都想要!
从零开始的ESP8266探索(11)-定时任务调度器Ticker使用演示
ESP8266 – WiFiUDP库