ESP8266 NTP time asynchron to other NTP devices

Hello,
I am using an ESP8266 with npt synce time. I have no problem getting the correct time. But the exact change of second seems to be off by somewhat half a second compared to other devices (Desktop PC, Atomic clock on mobile, ...).

Here is how I try to snyc to the exact time of changed second:

void showTime() {

  mark = time(&now);
  // wait for next second change
  while (time(&now) == mark);
  // new second, flash light
  digitalWrite(LEDW, HIGH); delay(40); digitalWrite(LEDW, LOW);
  sprintf(msg, "%d", time(&now));
  Serial.println(msg);
}

So why is the NTP synced time possibly off? NTP synchronization should be very exact, down to the ms. The delay seems to be constant over multiple restarts of the ESP8266. What am I possibly doing wrong?

Below is the full code, based on an example (NTP DST example

#include <ESP8266WiFi.h>            // we need wifi to get internet access
#include <time.h>                   // for time() ctime()
#include <coredecls.h> // optional settimeofday_cb() callback to check on server

#ifndef STASSID
#define STASSID "......."                            // set your SSID
#define STAPSK  "......."                        // set your wifi password
#endif
#define LEDB  D1
#define LEDW	D2

/* Configuration of NTP */
#define MY_NTP_SERVER "time.windows.com"           
#define MY_TZ "CET-1CEST,M3.5.0/02,M10.5.0/03"

/* Globals */
time_t now;                         // this are the seconds since Epoch (1970) - UTC
time_t mark;
char msg[50];

void time_is_set(bool from_sntp) {
  Serial.print(F("time was sent! from_sntp=")); Serial.println(from_sntp);
}

// modify ntp startup delay
uint32_t sntp_startup_delay_MS_rfc_not_less_than_60000 () {
  randomSeed(A0);
  return random(5000);
}

// modify ntp sync frequency
uint32_t sntp_update_delay_MS_rfc_not_less_than_15000 () {
  return 60 * 1000UL; // 1 minute
}

void showTime() {

  mark = time(&now);
  // wait for next second change
  while (time(&now) == mark);
  // new second, flash white LED
  digitalWrite(LEDW, HIGH); delay(40); digitalWrite(LEDW, LOW);
  sprintf(msg, "%d", time(&now));
  Serial.println(msg);
}

void setup() {
  settimeofday_cb(time_is_set); // callback sntp set
  pinMode(LEDB, OUTPUT);
  pinMode(LEDW, OUTPUT);

  Serial.begin(115200);
  Serial.println("\nNTP TZ DST - bare minimum");

  configTime(MY_TZ, MY_NTP_SERVER); 

  // start network
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  WiFi.begin(STASSID, STAPSK);
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print ( "." );
  }
  Serial.println("\nWiFi connected");
}

void loop() {
  showTime();
}

I did a short test - just to find out that my PC was off some seconds.
So I did a manual sync on my PC (the last sync of the PC was yesterday around 20:00).
Now the PC time and the ESP time fit very well together.
Tried both - the ESP32 and the ESP8266.

08:14:56.461 -> NTP TZ DST - bare minimum
08:14:56.695 -> ......
08:14:57.727 -> WiFi connected
08:14:57.727 -> 01:00:01
08:14:58.241 -> 01:00:02
08:14:58.241 -> 08:14:59
08:14:58.709 -> 08:15:00
08:14:59.670 -> 08:15:01
08:15:00.687 -> 08:15:02
08:15:01.676 -> 08:15:03
08:15:02.708 -> 08:15:04
08:15:03.675 -> 08:15:05
08:15:04.691 -> 08:15:06
08:15:05.695 -> 08:15:07
08:15:06.669 -> 08:15:08
08:15:07.703 -> 08:15:09
08:15:08.693 -> 08:15:10
08:15:09.676 -> 08:15:11
08:15:10.680 -> 08:15:12
08:15:11.669 -> 08:15:13
08:15:12.681 -> 08:15:14
08:15:13.702 -> 08:15:15
08:15:14.716 -> 08:15:16
08:15:15.665 -> 08:15:17
08:15:16.668 -> 08:15:18
08:15:17.686 -> 08:15:19
08:15:18.672 -> 08:15:20
08:15:19.676 -> 08:15:21
08:15:20.680 -> 08:15:22
08:15:21.703 -> 08:15:23
08:15:22.674 -> 08:15:24
08:15:23.707 -> 08:15:25
08:15:24.710 -> 08:15:26
08:15:25.665 -> 08:15:27
08:15:26.667 -> 08:15:28
08:15:27.673 -> 08:15:29
08:15:28.677 -> 08:15:30
[here I have updated the PC]
08:15:31.040 -> 08:15:31
08:15:32.042 -> 08:15:32
08:15:33.046 -> 08:15:33
08:15:34.048 -> 08:15:34
08:15:35.030 -> 08:15:35
08:15:36.057 -> 08:15:36
08:15:37.060 -> 08:15:37
08:15:38.065 -> 08:15:38
08:15:39.045 -> 08:15:39
08:15:40.072 -> 08:15:40
08:15:41.077 -> 08:15:41

that's my modified code:

/*
  NTP TZ DST - bare minimum - accuracy tests
  NetWork Time Protocol - Time Zone - Daylight Saving Time

  Our target for this MINI sketch is:
  - get the SNTP request running
  - set the timezone
  - (implicit) respect daylight saving time
  - how to "read" time to be printed to Serial.Monitor

  This example is a stripped down version of the NTP-TZ-DST (v2) from the ESP8266
  and contains some #precompiler defines to make it working for
  - ESP32 core 1.0.5, 1.0.6, 2.0.2, 2.0.16            https://werner.rothschopf.net/microcontroller/202103_arduino_esp32_ntp_en.htm
  - ESP8266 core 2.7.4, 3.1.2                         https://werner.rothschopf.net/202011_arduino_esp8266_ntp_en.htm


  by noiasca
  2021-03-28
  2023-06-18 minor changes
  2024-06-15 some tweaks to
*/

//#include <credentials.h>                               // if you have a config file with your credentials

#ifndef STASSID
#define STASSID "your-ssid"                            // set your SSID
#define STAPSK  "your-password"                        // set your wifi password
#endif

/* Configuration of NTP */
// choose the best fitting NTP server pool for your country
#define MY_NTP_SERVER "at.pool.ntp.org"  // 0 - 40
//#define MY_NTP_SERVER "time.windows.com"  // 60 - 100
//#define MY_NTP_SERVER "pool.ntp.org"  // 0 - 40

// choose your time zone from this list
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
#define MY_TZ "CET-1CEST,M3.5.0/02,M10.5.0/03" // Berlin, Vienna, Rom, ...

/* Necessary Includes */
#ifdef ARDUINO_ARCH_ESP32           // for the ESP32
#include <WiFi.h>                   // we need wifi to get internet access
#include "esp_sntp.h"               // used for sntp_set_sync_interval(uint32_t interval_ms)
#endif
#ifdef ARDUINO_ARCH_ESP8266         // for the ESP8266
#include <ESP8266WiFi.h>            // we need wifi to get internet access

uint32_t sntp_startup_delay_MS_rfc_not_less_than_60000 () {
    //info_sntp_startup_delay_MS_rfc_not_less_than_60000_has_been_called = true;
    return 60 *1000UL; // 60s (or lwIP's original default: (random() % 5000))
}
#endif
#include <time.h>                   // time() ctime()

void checkTime() {
  static time_t previous = 0;
  time_t now;                       // this are the seconds since Epoch (1970) - UTC
  tm tm;                            // the structure tm holds time information in a more convenient way
  time(&now);                       // read the current time
  if (now != previous) {
    previous = now;
    localtime_r(&now, &tm);           // update the structure tm with the current time
    char buffer[42] {0};              // a buffer large enough to hold your output
    strftime (buffer, sizeof(buffer), "%H:%M:%S", &tm);  // for different formats see https://cplusplus.com/reference/ctime/strftime/
    Serial.println(buffer);
  }
}

void setup() {
  Serial.begin(115200);
  delay(200); // just give Serial some time to start
  Serial.println(F("\nNTP TZ DST - bare minimum"));

#ifdef ARDUINO_ARCH_ESP32              // for the ESP32 (needs several lines)
  sntp_set_sync_interval(5 * 60 * 1000UL); // optional. 
  configTime(0, 0, MY_NTP_SERVER);     // 0, 0 because we will use TZ in the next line
  setenv("TZ", MY_TZ, 1);              // Set environment variable with your time zone
  tzset();
#endif
#ifdef ARDUINO_ARCH_ESP8266         // for the ESP8266
  configTime(MY_TZ, MY_NTP_SERVER); // the ESP8266 you only need this IMPORTANT ONE LINER in your sketch!
#endif

  // start network
  WiFi.persistent(false);
  WiFi.mode(WIFI_STA);
  WiFi.begin(STASSID, STAPSK);
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print('.');
  }
  Serial.println(F("\nWiFi connected"));
  // remark: by default, the NTP will be started after 60 secs
}

void loop() {
  checkTime();
}
//

P.S.:
When I try different NTP servers, the best result I get is when using the NTP pool for my country

// choose the best fitting NTP server pool for your country
#define MY_NTP_SERVER "at.pool.ntp.org"  // 0 - 40
//#define MY_NTP_SERVER "time.windows.com"  // 60 - 100
//#define MY_NTP_SERVER "pool.ntp.org"  // 0 - 40

Hello @hc2103 ,

Not really the answer to your question but I had (and still have to some extent) a similar problem. If you are interested you can read about it here:

I never really got a solution I was happy with and I've since moved on to other things. I don't know if I'll return to the problem.

I'm currently working with a Pi Pico WiFi, at some point I'll do a similar test on that to see how well it keeps time.

The lesson I learnt was to never write code that assumes a clock will always progress smoothly from one second to the next. Code should be written with the expectation of duplicate or skipped seconds, while at the same time trying to make sure there are no duplicate or skipped seconds.

My best guess is, because it's SNTP and not NTP.

Hello,
thank you all for your responses. I've been doing some additional research and tests which basically lead to the conclusions you all provided:

  • SNTP is not NTP, quote from RFC2030:

"SNTP servers should operate only at the root (stratum 1) of the subnet and then only in configurations where no other source of synchronization other than a reliable radio or modem time service is available. The full degree of reliability ordinarily expected of primary servers is possible only using the redundant sources, diverse subnet paths and crafted algorithms of a full NTP implementation."

  • the use of SNTP helps to mitigate the problem of drift of the local clock over longer periods of time. Frequent SNTP sync keeps the drift at an acceptable level

  • if more precision is needed, you will definitely need a reliable external timesource and ntp

I've done an experiment: used 2 identical D1 mini boards and loaded the identical sketch on both. Guess what - even on these 2 identical boards the signals (LED flashing) drift in relation to each other. So the precision of SNTP and the precision of the local hardware clock are not high enough to guarantee sub-second accuracy.

So, thank you again for your comments. I definitely learned to take care, not to rely on precise absolute timing (without using other means) too much.

1 Like

I've modified my sketch and uploaded it to two ESP8266.

when the second changes, the ESP prints to Serial.
Most of the time that happens to the same time (differences are in orange):

additionally I have added a LED and visually it appears they light up at the same time.

Just for experiments I have added a separate unsynced "manual" NTP call based on the common UDP example and extracted the milliseconds. The returned milliseconds look quite consistent to the PC time (more or less with a similar offset).

If you want to "sync" tasks between different devices why don't you implement your own UDP communication and let one device trigger a broadcast which could be used by others devices in your network?

In an ESP only network - I would even use ESP now.

@hc2103 What usecase do you want to solve?

Certain PC programs that set the NPT time will show you the results of each step, others will not. The first step in setting the time is measuring the total delay time between asking for the time and getting a response. one half of that delay time is added to the time returned by the new request so your PC is set as correctly as possible.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.