Arduino ESP8266 and NTP

I thought it would be a good idea to try and get one of these new ESP8266 WiFi boards and try get it to connect and download the time from an NTP server. I can see that there are examples in the IDE for Ethernet and WIFI shields but nothing for this new device (which isn't surprising) ;D

I'm using a Pro mini and I've managed to get everything connected and 'talking' but I'm stumped at the next step of how to get the public NTP server to send the information to me! Ive googled around but the information I'm finding is very opaque to me as I am unfamiliar with network packets and protocols. The code posted below sets up a connection and sends a message to the NTP server. The server replies with "No IP". This suggests to me that I am connected to the server but have not provided the correct message. Does anyone know the correct structure of the message (in an easy to understand way)!

Here is my code below as it might help others to get started but if you have any help you can offer, please do!

// // ESP8266 connection

/* Connect ESP8266 as follows
  I used an Arduino 3.3v Pro Mini with a 3.3V USB-FTDI converter 
  The ESP8266 was communicating at 9600 when delivered
  Vcc - 3.3v
  gnd - gnd
  rx - pin 11
  tx - pin 10
  ch_PD - pin 6
*/

// Target Access Point
#define ssid         "your ssid"
#define pass         "your password"

int EnablePin = 6;
unsigned long time = 0;

const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

#include <SoftwareSerial.h>

SoftwareSerial mySerial(10, 11); // RX, TX


void setup()  
{
  
  pinMode(EnablePin, OUTPUT);
  digitalWrite(EnablePin, LOW);
  delay (500);
  digitalWrite(EnablePin, HIGH);
  delay (500);
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  // set the data rate for the SoftwareSerial port
  mySerial.begin(9600);
  delay (1000);

  connectWiFi();  // Start the WiFi module
  
  setPacket();

}

void loop() // run over and over
{
  if (mySerial.available())
    Serial.write(mySerial.read());
  if (Serial.available())
    mySerial.write(Serial.read());
}

void connectWiFi()
{
  
//Rest the module.
Serial.println("AT+RST");
mySerial.println("AT+RST");
time = millis();
  while ((time + 2000) >= millis()){
   if (mySerial.available() > 0) {
   Serial.write(mySerial.read());
   }
  }

delay(20);

//Set the wireless mode
Serial.println("AT+CWMODE=1");
mySerial.println("AT+CWMODE=1");
time = millis();
  while ((time + 500) >= millis()){
   if (mySerial.available() > 0) {
   Serial.write(mySerial.read());
   }
  }
 delay(20);

//disconnect  - it shouldn't be but just to make sure
Serial.println("AT+CWQAP");
mySerial.println("AT+CWQAP");
time = millis();
  while ((time + 1000) >= millis()){
   if (mySerial.available() > 0) {
   Serial.write(mySerial.read());
   }
  } 
  
// connect to your wireless router  
String cmd="AT+CWJAP=\"";
cmd+=ssid;
cmd+="\",\"";
cmd+=pass;
cmd+="\"";
Serial.println(cmd);
mySerial.println(cmd);
//delay(2000);
time = millis();
  while ((time + 15000) >= millis()){
   if (mySerial.available() > 0) {
  Serial.write(mySerial.read());
  }
  } 

//print the ip addr
  mySerial.println("AT+CIFSR");
  Serial.println("ip address:");
  time = millis();
  while ((time + 5000) >= millis()){
   if (mySerial.available() > 0) {
   Serial.write(mySerial.read());
   }
  }
  
//set the single connection mode
Serial.println("AT+CIPMUX=0");
mySerial.println("AT+CIPMUX=0");
time = millis();
  while ((time + 1000) >= millis()){
   if (mySerial.available() > 0) {
   Serial.write(mySerial.read());
   }
  } 

//Connect to the NTP server

String cmd1 = "AT+CIPSTART=\"UDP\",\"";
cmd1 += "129.6.15.28";
cmd1 += "\",123";
mySerial.println(cmd1);
Serial.println(cmd1);
time = millis();
  while ((time + 5000) >= millis()){
   if (mySerial.available() > 0) {
   Serial.write(mySerial.read());
   }
  } 
}

void setPacket(){
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE); 
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49; 
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  
  //mySerial.write("192.168.0.13", 123);
  mySerial.write(packetBuffer,NTP_PACKET_SIZE);
 // Serial.println(packetBuffer,NTP_PACKET_SIZE);
  //Udp.endPacket();
}

I am pretty sure that the no ip message is from the esp8266, and means that the step where you set up the udp connection did not work.

There are some info here:
http://playground.arduino.cc/Code/NTPclient

Maybe it is easyer to use google:
http://www.esp8266.com/viewtopic.php?f=19&t=809

westfw:
I am pretty sure that the no ip message is from the esp8266, and means that the step where you set up the udp connection did not work.

When I ran the code again today I got a different result!!! :roll_eyes:

This time the response to the command

AT+CIPSTART="UDP","129.6.15.28",123

is "OK" suggesting the command was accepted.

However, after I send the packet I only get some garbage returned......as below....

===================================================================

AT+RST
AT+RST

OK
.ªЃÿP2ýPfhLÈ 4¥_PFˆ
[Vendor:www.ai-thinker.com Version:0.9.2.4]

ready
AT+CWMODE=1
AT+CWMODE=1

no change
AT+CWQAP
AT+CWQAP

OK
AT+CWJAP="xxxxxxxxxxxxx","xxxxxxxxxxxxx"
AT+CWJAP="xxxxxxxxxxxxx","xxxxxxxxxxxxx"

OK
ip address:
AT+CIFSR

192.168.0.13

OK
AT+CIPMUX=0
AT+CIPMUX=0

OK
AT+CIPSTART="UDP","129.6.15.28",123
AT+CIPSTART="UDP","129.6.15.28",123

OK
ã ì 1N14

=================================================================

So I think the connection today was good but I'm still stumped by what the format of the message to the server should be.

Erni:
There are some info here:
Arduino Playground - HomePage

Maybe it is easyer to use google:
Ask Google for the time - Everything ESP8266

I looked at these. The 2nd one may be the simplest and I'll try it in the next few days. As for the 1st link I couldnt make it work with this ESP8266 (I assume it needs the WIFI or Ethernet shield connected. Anyway I find the presentation of the page too complicated for my current competance. I tried extracting this portion of the code and just writing t to the serial port

// Send an NTP request
if (! (udp.beginPacket(timeServer, 123) // 123 is the NTP port
&& udp.write((byte *)&ntpFirstFourBytes, 48) == 48
&& udp.endPacket()))

Again I just seem to get garbage back, I wonder if the return info is not ASCII?

Assuming ASCII return is likely to be wrong.

THis may help you understand: 5. How does it work?

Paul

Not at all ascii: Structure of the NTP Data packet

westfw:
Not at all ascii: Structure of the NTP Data packet

Thanks - I found that page during my searches and it does provice me a bit of information but as I am unfamiliar with the syntax of datagrams and how to handle them it is still only part of the information. I'm really looking for the c code to drop into my program.

countrypaul:
Assuming ASCII return is likely to be wrong.

THis may help you understand: 5. How does it work?

Paul

Sorry, this link is to the main NTP org people who are all about setting up and running servers. There is no easy to read documentation here that I can find to show how to make a simple request to a server and to receive the appropriate response.

I am not interested in accuracy yet I thought it would be easy to just get the number of seconds since 1900,1,1 and just perform a simple calculation and hey presto get the correct date and time.

All I seem to be learning is the detail, ie how server work and how they calculate accuracy yet these websites and documentation assume a level of competancy that I do not have. I'm all up for doing the hard yards to learn but the sources I'm finding are all diving in way too deep.

I guess I'm remembering back o when I did work with GPS's and for that type of device you send a message and you receive a string back. I assumed NTP servers would be similar in their operation. If they simple, I have not yet uncovered the secret!

Maybe I need NTP for dummies! :confused:

Don't you get a "+ IPD, :" prefix, followed by a bunch of bytes? should be >= 48
typically, you'd search for that prefix, and then do a "serial.readbytes(packetbuffer, 48);" and look at the values in packetbuffer as binary. (actually, you'd normally make up a nice structure, like:

struct ntp_pkt_t {
       u_char li_vn_mode;   /* leap indicator, version and mode */
       u_char stratum;      /* peer stratum */
       u_char ppoll;        /* peer poll interval */
       s_char precision;    /* peer clock precision */
       u_fp   rootdelay;    /* distance to primary clock */
       u_fp   rootdispersion;      /* clock dispersion */
       u_int32       refid;        /* reference clock ID */
       l_fp   reftime;      /* time peer clock was last updated */
       l_fp   org;          /* originate time stamp */
       l_fp   rec;          /* receive time stamp */
       l_fp   xmt;          /* transmit time stamp */
       u_int32       exten[1];     /* misused */
       u_char mac[MAX_MAC_LEN]; /* mac */
} ntppkt;

and use that for both transmit/receive of the packet.
(ie "Serial.write(&ntppkt, sizeof(ntppkt));")

NTP is not a simple protocol. It has things in it to synchronize time across networks to within very small times - much less than a network transmission delay. Which is a really neat trick, if you think about it.
You might want to look into SNTP ("Simple Network Time Protocol"), Time Protocols (port 37), and unix "rdate", which I think are all more of the "tell me what time it is and I won't worry about how much time has passed since I first asked."
(OTOH, I don't know if they are as widely implemented...)

Thanks for your help westfw. I've added a command to prepare the port for sending data (and now I get the IPD message)

AT+CIPSEND=48

The result/response is:

===================================================================

OK
AT+CIPSTART="UDP","129.6.15.28",123
AT+CIPSTART="UDP","129.6.15.28",123

OK
AT+CIPSEND=48
AT+CIPSEND=48

ãì 1N14

SEND OK

+IPD,48:$ãACTSØ7×0Æêä

OK

=================================================================

I tried using your code "mySerial.readBytes(packetBuffer, 48);" but it returns a error:

"invalid conversion from 'byte*' to 'char*'"

sketch_dec14a.ino: In function 'void setPacket()':
sketch_dec14a:177: error: invalid conversion from 'byte*' to 'char*'
sketch_dec14a:177: error: initializing argument 1 of 'size_t Stream::readBytes(char*, size_t)'

the packetBuffer was declared as a byte at the top of the program but is clearly not happy with this way of reading the data

hmmm one step forward. You may be right about using the SNTP. maybe i should investigate that option!

btw I am using a structure similar to the one you suggested (copied from an arduino example). It is at the end of my program thus:

void setPacket(){
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE); 
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49; 
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  
  //mySerial.write("192.168.0.13", 123);
  mySerial.write(packetBuffer,NTP_PACKET_SIZE);
  
  delay (2000);
  
  mySerial.readBytes(packetBuffer, 48);
 // Serial.println(packetBuffer,NTP_PACKET_SIZE);
  //Udp.endPacket();
}

Well I did not get my own code to work :-*

but I did find an update to the firmware which includes a direct call to a SNTP server.

If anyone is interested it can be found here:

http://www.esp8266.com/viewtopic.php?f=11&t=705&p=4881#p4881

Hope this helps somebody!

Can someone please show us what an NTP request packet looks like? I see its a 48 byte thing. How can this sent via esp8266? Can it be converted to a string and back to hex bytes?

Thanks.

Tim

I'm working on the same thing, here is my capture of the NTP request and answer, both are valid and captured at the wifi hotspot, I'm working on the receiving part at this moment.

Command sent by my ESP8266 to my NTP server (from 192.168.0.242 to 192.168.0.28)

0000   64 66 b3 b0 f1 20 18 fe 34 9f 38 5e 08 00 45 00  df... ..4.8^..E.
0010   00 4c 00 4e 00 00 ff 11 38 f4 c0 a8 00 f2 c0 a8  .L.N....8.......
0020   00 1c 5b 4b 00 7b 00 38 37 6c e3 00 06 ec 00 00  ..[K.{.87l......
0030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0040   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0050   00 00 00 00 00 00 00 00 00 00                    ..........

Command sent back by the NTP server

0000   18 fe 34 9f 38 5e 64 66 b3 b0 f1 20 08 00 45 c0  ..4.8^df... ..E.
0010   00 4c 00 00 40 00 40 11 b7 82 c0 a8 00 1c c0 a8  .L..@.@.........
0020   00 f2 00 7b 5b 4b 00 38 36 2a 24 03 06 f0 00 00  ...{[K.86*$.....
0030   0b 34 00 00 16 67 5b 79 a5 92 d9 08 d2 c7 ee d1  .4...g[y........
0040   ff 40 00 00 00 00 00 00 00 00 d9 08 d4 b3 94 c4  .@..............
0050   37 25 d9 08 d4 b3 94 db 47 72                    7%......Gr

OK, got it working (ESP8266 in AT mode, fully reset and connected to wifi )

const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets 

unsigned long GetTime()
{
  String cmd = "AT+CIPSTART=\"UDP\",\"192.168.0.28\",123"; // my own NTP server, synced by GPS
  Serial1.println(cmd);
  delay(2000);
  if(Serial1.find("Error")){
    Serial.print("RECEIVED: Error");
    return 0;
  }
  
  memset(packetBuffer, 0, NTP_PACKET_SIZE); 
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  
  Serial1.print("AT+CIPSEND=");
  Serial1.println(NTP_PACKET_SIZE);
  if(Serial1.find(">"))
  {
    for (byte i = 0; i < NTP_PACKET_SIZE; i++)
    {
      Serial1.write(packetBuffer[i]);
      delay(5);
    }
  }else{
    Serial1.println("AT+CIPCLOSE");
    return 0;
  }
  
  //Serial1.find("+IPD,48:");
  
  int acksize = NTP_PACKET_SIZE + 1 + 2 + 8; // ESP8266 adds a space, a CRLF and starts with "+IPD,48:"
  
  Serial.println("ESP2866 ACK : ");
  for (byte i = 0; i < acksize; i++)
  {
    while (Serial1.available() == 0) // you may have to wait for some bytes
    {
      //Serial.print(".");
    }
    byte ch = Serial1.read();
    if (ch < 0x10) Serial.print('0');
    Serial.print(ch,HEX);
    Serial.print(' ');
    if ( (((i+1) % 15) == 0) ) { Serial.println(); }
  }
  Serial.println();
  Serial.println();

  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  
  Serial.println("Server answer : ");

  int i = 0;
  while (Serial1.available() > 0) {
    byte ch = Serial1.read();
    if (i <= NTP_PACKET_SIZE)
    { 
      packetBuffer[i] = ch;
    }
    if (ch < 0x10) Serial.print('0');
    Serial.print(ch,HEX);
    Serial.print(' ');
    if ( (((i+1) % 15) == 0) ) { Serial.println(); }
    delay(5);
    i++;
    if ( ( i < NTP_PACKET_SIZE ) && ( Serial1.available() == 0 ) )
    {
      while (Serial1.available() == 0)  // you may have to wait for some bytes
      {
        Serial.print("!");
      }
    }
  }
  
  Serial.println();
  Serial.println();
  Serial.print(i+1);
  Serial.println(" bytes received"); // will be more than 48
  
  Serial.print(packetBuffer[40],HEX);
  Serial.print(" ");
  Serial.print(packetBuffer[41],HEX);
  Serial.print(" ");
  Serial.print(packetBuffer[42],HEX);
  Serial.print(" ");
  Serial.print(packetBuffer[43],HEX);
  Serial.print(" = ");

  unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
  unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);  
    // 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;
  
  Serial.print(secsSince1900,DEC);
  
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
  const unsigned long seventyYears  = 2208988800UL;
    // subtract seventy years:
  unsigned long epoch = secsSince1900 - seventyYears;
  
  unsigned long DST = 60*60*2; // adjust to your GMT+DST
  
  unsigned long timestamp = epoch + DST;

  Serial.println();
  Serial.print("Epoch : ");
  Serial.println(epoch,DEC);        

  return timestamp;
}

you can use the output of this function to set the time of the Time library

1 Like

Thanks pixelk, that code is awesome. I modified it slightly so it doesn't get caught in a while loop and just changed the NTP server to one from pool.ntp.org: the internet cluster of ntp servers :

const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

unsigned long GetTime()
{

 String cmd = "AT+CIPSTART=\"UDP\",\"130.102.128.23\",123"; // NTP server
 Serial1.println(cmd);
 delay(2000);
 if(Serial1.find("Error")){
   Serial.print("RECEIVED: Error");
   return 0;
 }
 int counta = 0;
 memset(packetBuffer, 0, NTP_PACKET_SIZE);
 // Initialize values needed to form NTP request
 // (see URL above for details on the packets)
 packetBuffer[0] = 0b11100011;   // LI, Version, Mode
 packetBuffer[1] = 0;     // Stratum, or type of clock
 packetBuffer[2] = 6;     // Polling Interval
 packetBuffer[3] = 0xEC;  // Peer Clock Precision
 
 Serial1.print("AT+CIPSEND=");
 Serial1.println(NTP_PACKET_SIZE);
 if(Serial1.find(">"))
 {
   for (byte i = 0; i < NTP_PACKET_SIZE; i++)
   {
     Serial1.write(packetBuffer[i]);
     delay(5);
   }
 }else{
   Serial1.println("AT+CIPCLOSE");
   return 0;
 }
 
 //Serial1.find("+IPD,48:");
 
 int acksize = NTP_PACKET_SIZE + 1 + 2 + 8; // ESP8266 adds a space, a CRLF and starts with "+IPD,48:"
 
 Serial.println("ESP2866 ACK : ");
 for (byte i = 0; i < acksize; i++)
 {
     while (Serial1.available() == 0)  // you may have to wait for some bytes
     {
       counta += 1;
       Serial.print(".");
       delay(100);
       if (counta == 15){
         return 0;
       }
     }
   byte ch = Serial1.read();
   if (ch < 0x10) Serial.print('0');
   Serial.print(ch,HEX);
   Serial.print(' ');
   if ( (((i+1) % 15) == 0) ) { Serial.println(); }
 }
 Serial.println();
 Serial.println();

 memset(packetBuffer, 0, NTP_PACKET_SIZE);
 
 Serial.println("Server answer : ");

 int i = 0;
 while (Serial1.available() > 0) {
   byte ch = Serial1.read();
   if (i <= NTP_PACKET_SIZE)
   {
     packetBuffer[i] = ch;
   }
   if (ch < 0x10) Serial.print('0');
   Serial.print(ch,HEX);
   Serial.print(' ');
   if ( (((i+1) % 15) == 0) ) { Serial.println(); }
   delay(5);
   i++;
   if ( ( i < NTP_PACKET_SIZE ) && ( Serial1.available() == 0 ) )
   {
     while (Serial1.available() == 0)  // you may have to wait for some bytes
     {
       counta += 1;
       Serial.print("!");
       delay(100);
       if (counta == 15){
         return 0;
       }
     }
   }
 }
 
 Serial.println();
 Serial.println();
 Serial.print(i+1);
 Serial.println(" bytes received"); // will be more than 48
 
 Serial.print(packetBuffer[40],HEX);
 Serial.print(" ");
 Serial.print(packetBuffer[41],HEX);
 Serial.print(" ");
 Serial.print(packetBuffer[42],HEX);
 Serial.print(" ");
 Serial.print(packetBuffer[43],HEX);
 Serial.print(" = ");

 unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
 unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);  
   // 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;
 
 Serial.print(secsSince1900,DEC);
 
   // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
 const unsigned long seventyYears  = 2208988800UL;
   // subtract seventy years:
 unsigned long epoch = secsSince1900 - seventyYears;
 
 unsigned long DST = 60*60*2; // adjust to your GMT+DST
 
 unsigned long timestamp = epoch + DST;

 Serial.println();
 Serial.print("Epoch : ");
 Serial.println(epoch,DEC);
 return epoch;
}

I used the above code of yours and get server answer. but it does not go above i= 29;

ESP2866 ACK :
FF 0D 0A 52 65 63 76 20 34 38 20 62 79 74 65
73 0D 0A 0D 0A 53 45 4E 44 20 4F 4B 0D 0A .0D
0A 2B 49 50 44 2C 34 38 3A 24 02 06 EC 00 00
2A 9E 00 00 06 5B D8 DA C0 CA D9 B6 07 7E

Server answer :
8A DB C5 A2 00 00 00 00 00 00 00 00 D9 B6 09
A7 3B 25 B3 6C D9 B6 09 A7 3B 27 3C FA !!!!!!!!!!!!!!

I recive Server answer too :
8B 23 06 57 00 00 00 00 00 00 00 00 D9 CE 21
5B 69 B7 58 A0 D9 CE 21 5B 69 B8 C3 9E !!!!!!!!!!!!!!

Please give the working code somebody.