ESP8266: NTP seems to lose minutes over time

I'm facing a weird behavior (bug :slight_smile: ) in my code about the use of NTP. I periodically send a request and parse the answer (if any...). Initially the data retrieved is accurate, i.e. is the same of my PC. But after a couple of days it returns the correct data, hours and seconds, but exactly 10 minutes ahead.

Here some code:

WiFiUDP udp;
WiFiClient wifiClient;
char answer[200];
char datetimeConn[32];
long millisNow, millisPrev;

#define TMR_NTP_REQUEST_PERIOD_TIMEOUT      10 // s
#define TMR_NTP_REQUEST_PERIOD              10 // minutes
#define TMR_NTP_TIMEOUT                     2  // s

int tmrNTP = 0;

typedef enum StatesNTP
{
  S_NTP_IDLE,
  S_NTP_WAITING  
} StatesNTP;
StatesNTP stateNTP = S_NTP_IDLE;

void fsmNTP()
{
  // check if we are online
  if (stateNET != S_NET_ONLINE) return;
  switch (stateNTP)
  {
  case S_NTP_IDLE:
    if (tmrNTP == 0)
    {
      // time to send a request
      WiFi.hostByName(ntpServerName, timeServerIP); 
      NTP_SendRequest(timeServerIP);
      tmrNTP = TMR_NTP_TIMEOUT;
      stateNTP = S_NTP_WAITING;
    }
    break;

  case S_NTP_WAITING:
    if (tmrNTP == 0)
    {
      if (udp.parsePacket())
      {
        // packet received!
        NTP_ParsePacket();   
        tmrNTP = TMR_NTP_REQUEST_PERIOD * 60;
      }
      else
      {
        // timeout :(
        tmrNTP = TMR_NTP_REQUEST_PERIOD_TIMEOUT;
      }
      stateNTP = S_NTP_IDLE;
    }
    break;
  }
}


void NTP_ParsePacket()
{
  udp.read(packetBuffer, NTP_PACKET_SIZE); 
  unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
  unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
  unsigned long secsSince1900 = highWord << 16 | lowWord;

  const unsigned long seventyYears = 2208988800UL;
  unsigned long epoch = secsSince1900 - seventyYears;

  struct tm ts = *localtime((long int *) &epoch);
    
  strcpy(answer, "NTP,");
  strftime(answer + 4, sizeof(answer), "%Y,%m,%d,%H,%M,%S", &ts);
  Serial.println(answer);
}

void setup()
{
  // connect to WiFi and other stuff
  millisNow = millisPrev = millis();
}

void loop() 
{
  fsmNTP();

  // Timers 
  millisNow = millis();
  if (millisNow - millisPrev > 1000)
  {
    millisPrev = millisNow;
    if (tmrNTP) tmrNTP--;
  }
}

Is there anything obviously wrong here?

  unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
  unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
  unsigned long secsSince1900 = highWord << 16 | lowWord;

Obviously, you understand the need to shift the high 2 bytes and or in the log two bytes. So, why use the word macro? Do you KNOW exactly how it works?

  struct tm ts = *localtime((long int *) &epoch);

Does localtime() really expect a signed value?

  char answer[200];

  strcpy(answer, "NTP,");
  strftime(answer + 4, sizeof(answer), "%Y,%m,%d,%H,%M,%S", &ts);
  Serial.println(answer);

Do you buy pants that are 10 times too big, too?

  millisNow = millis();
  if (millisNow - millisPrev > 1000)

There is nothing magic about the letters 'm', 'i', 'l', 'l', 'i', and 's' in the name. If a different name makes more sense (and it DOES), then use a more meaningful name. now and lastTimeCheck tell ME exactly what is happening.

I have NO idea what event time is stored in millisPrev.

what is the purpose of this exercise?

Thanks for your kind comments.
I try to answer you:

  • the code is from the official Arduino docs!. I bet people with more experience than me wrote it.
  • the buffer is not used for the date time only, obiouvsly
  • what's wrong with the software timer? I use it since a long time with no problems. Whenever 1 second has elapsed I store the current value of millis() so I can wait another second from now. I'm sorry but I cannot understand what you're saying (i.e. "There is nothing magic about the letters 'm', 'i', 'l', 'l', 'i', and 's' in the name"). If you're afraid about the first value of millisPrev is set equal to millis() in setup().

I'm sorry but I cannot understand what you're saying (i.e. "There is nothing magic about the letters 'm', 'i', 'l', 'l', 'i', and 's' in the name"). If you're afraid about the first value of millisPrev is set equal to millis() in setup().

There is nothing magic about the letters millis in the name of the variable. If it makes more sense to name the variable for the event that the time relates to, and it almost always does, then use a more meaningful name.

As to why you are seeing a time discrepancy, I think you need to print out ALL of the NTP servers response. Perhaps a clue by four will whack you.

see the BlinkWithouDelay example to learn how to execute something every x milllis.

there are ntp client libraries in Library Manager. I recommend the NtPClientLib by German Martin

Arduino can keep time with Time library without requesting time every 2 seconds from ntp

perhaps the ntp server gives you a ban for annoyance

Juraj:
Arduino can keep time with Time library without requesting time every 2 seconds from ntp

I know this, but to test the function I send the request every 10 minutes (not 2 seconds).

perhaps the ntp server gives you a ban for annoyance

mmm... I actually receive the packets, but after a couple of days are 10 minutes wrong.

I'm sorry, but did you (both) actually see the built-in example of the NTP client? It's exacly the same code:

  if (Udp.parsePacket()) {
    // We've received a packet, read the data from it
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    // the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, extract the two words:

    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("Seconds since Jan 1 1900 = ");
    Serial.println(secsSince1900);

    // now convert NTP time into everyday time:
    Serial.print("Unix time = ");
    // 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;
    // print Unix time:
    Serial.println(epoch);

    // ....

I'm sorry, but did you (both) actually see the built-in example of the NTP client? It's exacly the same code

But, was it written for your hardware?

Mark81:
I know this, but to test the function I send the request every 10 minutes (not 2 seconds).
mmm... I actually receive the packets, but after a couple of days are 10 minutes wrong.

I was more afraid about this rather than the packet decoding.

If you request a packet every 10 minutes, and after some time, the time in the packet is off by 10 minutes, then I don't think you are parsing the latest packet.

Try changing the request interval to 7 minutes. If, after some time, the time is off by 7 minutes, then definitely you are not parsing the correct packet.

PaulS:
But, was it written for your hardware?

Shouldn't the Arduino porting take care of that?

Mark81:
Shouldn't the Arduino porting take care of that?

Assuming that things like the word() macro are properly written for all hardware, yes.

Am I willing to assume that? No.

I don't know what's wrong with your code, but I know that this works:
https://tttapa.github.io/ESP8266/Chap15%20-%20NTP.html

Pieter

I periodically send a request and parse the answer (if any...). Initially the data retrieved is accurate, i.e. is the same of my PC. But after a couple of days it returns the correct data, hours and seconds, but exactly 10 minutes ahead.

Re: ESP8266: NTP seems to lose minutes over time

I am confused. Is the time parsed from the NTP packet gaining or loosing 10 minutes?