1415926 发表于 2015-10-20 13:36:40

贡献一段使用ESP8266连接NTP服务器获取时间的代码

做了个可以自动对时的时钟,没有从网上找到合适的读取NTP服务器时间的代码,自己写了一个,贡献出来,供需要的人参考。
使用的是ESP8266的AT指令,没有使用任何的库
有好几个函数,入口是第一个:synchDateTimeFromNTPServer()
比较粗糙,有很多地方没有判断指令是否执行成功。




/**
* 网络对时
*/
void synchDateTimeFromNTPServer(){
String cmd, respMessage;
byte packetBuffer;

cmd = "AT+CIPMUX=0";
respMessage = execATCommand(cmd, 500, false);

//Connect to the NTP server
cmd = "AT+CIPSTART=\"UDP\",\"1.cn.pool.ntp.org\",123";
respMessage = execATCommand(cmd, 500, false);

cmd = "AT+CIPSEND=48";
respMessage = execATCommand(cmd, 50, false);

memset(packetBuffer, 0, 48);
packetBuffer = 0b11100011;   // LI, Version, Mode
packetBuffer = 0;   // Stratum, or type of clock
packetBuffer = 6;   // Polling Interval
packetBuffer = 0xEC;// Peer Clock Precision
packetBuffer= 49;
packetBuffer= 0x4E;
packetBuffer= 49;
packetBuffer= 52;

Serial.setTimeout(30000);
while(Serial.available() > 0){
    Serial.read();
}
Serial.write(packetBuffer, 48);
String data = "";
unsigned long t1 = millis();
byte ntpPackage;
do{
    char r = Serial.read();
    if(r == '\n' || r == '\r'){
      data = "";
    }else if( r >= 0){
      data += r;
    }

    if(data == "+IPD,48:"){
      Serial.readBytes(ntpPackage, 48);
      parserNTPMessage(ntpPackage);
      break;
    }
}while(millis() - t1 < 30000);

cmd = "AT+CIPCLOSE";
respMessage = execATCommand(cmd, 200, true);
}


/**
* 解析NTP服务器返回的报文,
*
*/
void parserNTPMessage (byte ntpPackage[]){
byte li = (byte) ((ntpPackage >> 6) & 0x3);
byte ver = (byte) ((ntpPackage >> 3) & 0x7);
byte mode = (byte) (ntpPackage & 0x7);
//Serial.println("LI=" + String(li) + "; version=" + String(ver) + "; mode=" + String(mode));
if(li == (byte) 11){
    //11表示当前不可对时(服务器处于闰秒状态)
    return;
}
unsigned long highWord = word(ntpPackage, ntpPackage);
unsigned long lowWord = word(ntpPackage, ntpPackage);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;

unsigned long secs = secsSince1900 + 8 * 3600L; //东八区,加8小时

int y = 1900, mon, d, h, m, s, wk;

wk = (secs / 86400L) % 7 + 1; //86400 is secons in one day; +1 for 1900/1/1 is Monday

do{
    unsigned long ys;
    if(( y % 4 == 0 && y % 100 != 0) || y % 400 == 0){
      ys = 31622400L; //31622400 = 366 * 24 * 3600;
    }else{
      ys = 31536000L;// 31536000 = 365 * 24 * 3600;
    }
    if(secs < ys){
      break;
    }else{
      secs -= ys;
      y++;
    }
}while(1);

int mons = {31,28,31,30,31,30,31,31,30,31,30,31};
if((y % 4 == 0 && y % 100 != 0) || y % 400 == 0){
    mons = 29;
}
for(mon=0; mon < 12; mon ++){
    if(secs < mons * 86400L){
      break;
    }else{
      secs -= mons * 86400L;
    }
}

d = secs / 86400L + 1; //86400 = 24 * 3600 = how many seconds in a day
secs = secs % 86400L;

h = secs / 3600;
secs = secs % 3600;
m = secs / 60;
s = secs % 60;

//NTP服务器返回的时间已经解析完毕
//TODO:如果需要更精确的时间,需要处理报文中的服务器收到请求和处理完毕的时间戳;
//记录arduino发送NTP request和收到response的时间戳,
//计算网络消耗掉的时长并加到这里获得的时间上

         
//char buf;
//snprintf(buf, sizeof(buf), "%04d/%02d/%02d %02d:%02d:%02d %0d", y, mon + 1, d, h, m, s, wk);
//Serial.print("DATE: ");
//Serial.println(buf);
}


/**
* 向TCP Server发送消息
*/
void sendMessage(String msg){
String cmd = "AT+CIPSEND=" + String(msg.length());
execATCommand(cmd, 0, false);
delay(10);
//TODO:没有判断命令是否成功执行
Serial.print(msg);
}


/**
* 获取ESP8266 IP地址
*/
String getIPAddr(){
String msg = execCommand("AT+CIFSR", 100);
String ip = msg.substring(23,38);
if(ip.indexOf("\"") > 0){
    ip = ip.substring(0, ip.indexOf("\""));
}
return ip;
}


String execCommand(String cmd, int timeout){
return execATCommand(cmd, timeout, true);
}


/**
* 执行一个AT命令,并返回ESP8266的返回值
*/
String execATCommand(String cmd, int timeout, boolean clearCache){
//执行AT命令前,先把缓存内的数据都读完(不用)防止影响命令结果
if(clearCache){
    while( Serial.available() > 0) {
      Serial.read();
    }
   
}
Serial.print(cmd);
Serial.print("\r\n");
String data = "";
if(timeout > 0){
    //delay(50);
    long t1 = millis();
    //while(Serial.available() > 0){
    do{
      char r = Serial.read();
      if(r < 0){
          //r==-1 if nothing read
          continue;
      }
      if(r == '\r') {
          //舍弃回车,仅仅保留换行
      } else {
            data += r;
      }
    }while((millis() - t1) < timeout);
}
return data;
}

wetnt 发表于 2015-10-20 17:05:03

收藏非常不错,有机会测试下了!

maidoo 发表于 2015-11-16 15:51:58

最近正考虑NTP的方案改进家里的时钟呢,正好,太有帮助了。

fsj5098 发表于 2017-4-16 22:59:40

最近正考虑NTP的方案改进家里的时钟呢

qjyjack612510 发表于 2017-5-25 12:27:44

谢谢楼主,才学的,就想做个自动对时的钟。。。收下。。

ppc888 发表于 2017-5-28 00:47:52

本帖最后由 ppc888 于 2017-5-28 00:49 编辑

试了一下月份少1   难道我在移植过程中弄坏了 :Q

sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900 = 3704892427
Unix time = 1495903627
2017-5-28 0:47:7 --- wk7


显示正确的因为在显示时补加了1
Serial.print(mon+1);

mondaywoo 发表于 2017-5-30 19:07:22

初学者学习,感谢分享

1415926 发表于 2017-7-21 16:46:02

ppc888 发表于 2017-5-28 00:47
试了一下月份少1   难道我在移植过程中弄坏了

sending NTP packet...


1月用0;2月用1……12月用11表示,这个是大部分编程语言的习惯。

samsung206bw 发表于 2017-10-10 21:45:29

真不错!一下就成功了!!!:)

super-power 发表于 2019-2-5 13:02:49

请问楼主,8266芯片是不是要设置成wifi透传模式?
页: [1]
查看完整版本: 贡献一段使用ESP8266连接NTP服务器获取时间的代码