NodeMCU(ESP8266)获取NTP时间

很久没有搞ESP8266了,可能是这两年工作太忙了,又或者是对生活失去了斗志,所以最近又重新把两年前的东西重新收拾收拾。

NTP协议

我之前有写一篇NTP 入门介绍,大家如果有对NTP不了解的,可以先查阅这篇《NTP 入门介绍

为啥要同步时间

之前看到有一个用户通过esp8266做了一个时钟出来( ESP8266物联网创意点阵时钟,女朋友看了都想要!
),自己也想搞一个类似的,然后就发现他有一个功能就是网络自动校准时间,才了解到有NTP这个协议的存在,所以就找到一些代码研究了一番,所以就有了今天这篇博文。

实现思路

esp8266感觉是一个很简单的东西,网上有很多的代码示例,这里我更推荐使用官方的示例库。

如何从官方的示例库中找到我们要用的示例代码:

代码

根据上述的示例和其他用户分享的库文件使用方法,我们稍微整理一下,把代码改成我们想要的样子。

  1. 先连接wifi
  2. 判断时间是否是正确的,如果不正确就去同步时间
  3. 如果时间正确就每秒中增加我们的时钟信息,顺便打印出来

上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#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库