参考资料
准备
APP:
https://github.com/EspressifApp/EspRelease/tree/master/EspTouch
开发板一块
源码地址
https://github.com/zhqi-ang/esp32
SNTP
国内网址
1.cn.pool.ntp.org
2.cn.pool.ntp.org
3.cn.pool.ntp.org
0.cn.pool.ntp.org
UDP通讯,所以要创建UDP Client Socket;
端口:123
需要先将域名转为IP地址,然后进行信息传输;
NTP工作原理
NTP的基本工作原理如图所示。Device A和Device B通过网络相连,它们都有自己独立的系统时钟,需要通过NTP实现各自系统时钟的自动同步。为便于理解,作如下假设:
在Device A和Device B的系统时钟同步之前,Device A的时钟设定为10:00:00am,Device B的时钟设定为11:00:00am。
Device B作为NTP时间服务器,即Device A将使自己的时钟与Device B的时钟同步。
NTP报文在Device A和Device B之间单向传输所需要的时间为1秒。
系统时钟同步的工作过程如下:
-
Device A发送一个NTP报文给Device B,该报文带有它离开Device A时的时间戳,该时间戳为10:00:00am(T1)。
-
当此NTP报文到达Device B时,Device B加上自己的时间戳,该时间戳为11:00:01am(T2)。
-
当此NTP报文离开Device B时,Device B再加上自己的时间戳,该时间戳为11:00:02am(T3)。
-
当Device A接收到该响应报文时,Device A的本地时间为10:00:03am(T4)。
至此,Device A已经拥有足够的信息来计算两个重要的参数:
-
NTP报文的往返时延Delay=(T4-T1)-(T3-T2)=2秒。
-
Device A相对Device B的时间差offset=((T2-T1)+(T3-T4))/2=1小时。
这样,Device A就能够根据这些信息来设定自己的时钟,使之与Device B的时钟同步。
报文格式
为什么是48个字节?
从上图可知,第一行32个bit,然后是Root delay …Transmit timestamp;这些加起来一共就是48个byte了
最后一个Authenticator 是可选的!所以一般不选^^
主要字段的解释如下:
-
LI:当前时间闰秒标志。字段长度为2位bit,只在服务器端有效。取值定义为:
LI=0:无警告;
LI=1:最后一分钟是61秒;
LI=2:最后一分钟是59秒;
LI=3:警告(时钟没有同步)
服务器在开始时,LI设置为3,一旦与主钟取得同步后就设置成其它值。
-
VN(Version Number):长度为3比特,表示NTP的版本号,可以是3或者是4
-
Mode:指示协议模式。字段长度为3位,取值定义为:
Mode=0:保留
Mode=1:对称主动;
Mode=2:对称被动;
Mode=3:客户;
Mode=4:服务器;
Mode=5:广播;
Mode=6:保留为NTP控制信息;
Mode=7:保留为用户定义;
在单播和多播模式,客户在请求时把这个字段设置为3,服务器在响应时把这个字段设置为4。在广播模式下,服务器把这个字段设置为5。
-
Poll Interval:指示数据包的最大时间间隔,以秒为单位,作为2的指数方的指数部分,该字段只在服务器端有效。字段长度为8位整数,取值范围从4-17,即16秒到131,072秒。
-
Precision:指示系统时钟的精确性,以秒为单位,作为2的指数方的指数部分,该字段只在服务器端有效。字段长度为8位符号整数,取值范围从-6到-20。
-
Root Delay:指示与主时钟参考源的总共往返延迟,以秒为单位,该字段只在服务器端有效。字段长度为32位浮点数,小数部分在16位以后,取值范围从负几毫秒到正几百毫秒。
-
Root Dispersion:指示与主时钟参考源的误差,以秒为单位,该字段只在服务器端有效。字段长度为32位浮点数,小数部分在16位以后,取值范围从零毫秒到正几百毫秒。
-
Reference Identifier:指示时钟参考源的标记,该字段只在服务器端有效。对于一级服务器,字段长度为4字节ASCII字符串,左对齐不足添零。对于二级服务器,在IPV4环境下,取值为一级服务器的IP地址,在IPV6环境下,是一级服务器的NSAP地址。
-
Reference Timestamp:指示系统时钟最后一次校准的时间,该字段只在服务器端有效,以前面所述64位时间戳格式表示。
-
Originate Timestamp:指示客户向服务器发起请求的时间,以前面所述64位时间戳格式表示。
-
Receive Timestamp:指服务器收到客户请求的时间 ,以前面所述64位时间戳格式表示。
-
Transmit Timestamp:指示服务器向客户发时间戳的时间,以前面所述64位时间戳格式表示。
-
Authenticator(可选):当需要进行SNTP认证时,该字段包含密钥和信息加密码
发送请求
必须:LI、VN、Mode
可选:Originate Timestamp(由上面原理可知,如果需要更准确就需要加这个了;但一般不太需要~所以为0)
LI为00;VN为011,;Mode为011
b 00 011 011 = b 0001 1011 = 0x1B
接收请求
就是读取Transmit Timestamp;
这个是时间戳,需要进行转换,
首先减去1970;
#define TIME1970 2208988800
然后GMT+8,加上8小时;
再转换下,就能到年月日了
部分代码
详细代码,请看上面给得链接
memset(data, 0, 48);
data[0] = 0x1b;
ret = sendto(socket_fb, data, 48, 0, (struct sockaddr *)&serv_addr, SIZE_SOCKADDR_IN);
do {
vTaskDelay(10 / portTICK_PERIOD_MS);
str_lent = recvfrom(socket_fb , data, 48, 0, (struct sockaddr*)&serv_addr, &SIZE_SOCKADDR_IN);
} while(str_lent != 48);
if(str_lent == 48)
{
time_t t = 0;
for(int i = 40; i <= 43; i++)
{
uint8_t c = (unsigned char)data[i];
t = (t<<8) | c;
}
t -= TIME1970; // 1970.1.1 0:0:0
t += 8 * 60 * 60; // UTC to Beijin
struct tm now_time;
localtime_r(&t, &now_time);
ESP_LOGE(TAG, "%d-%d-%d %d:%d:%d\n", now_time.tm_year + 1900, now_time.tm_mon + 1,
now_time.tm_mday, now_time.tm_hour, now_time.tm_min, now_time.tm_sec);
}