ESP32 SNTP: Additional features

Introduction

In this tutorial we are going to learn some additional functionalities from the ESP32 system time and SNTP synchronization, using the Arduino core. As such, it assumes that the previous tutorial explaining the basics of the ESP32 system time and SNTP was already followed.

In our code, we are still going to use the Arduino core configTime function, which allows to perform the time zone and SNTP initialization very easily. Nonetheless, the rest of the functions we will use don’t have Arduino wrappers, meaning that we are going to directly interact with IDF’s system time API. However, all these functions are really simple to use.

In the 3 sections below, we are going to cover the following functionalities:

  • Getting the SNTP sync interval configured.
  • Setting a new SNTP sync interval.
  • Configuring a callback function to execute when there is a SNTP sync.

The tests shown below were performed on a ESP32-E FireBeetle board from DFRobot. The Arduino core version used was 2.0.0.

Getting the SNTP sync interval

In this section we will learn how to obtain the configured SNTP sync interval. This can be done simply by calling the sntp_get_sync_interval function.

This function takes no arguments and returns as output the sync interval, in milliseconds. This value is returned as a uint32_t, meaning we can directly print it to the serial port, like shown below.

Serial.println(sntp_get_sync_interval());

The snipped below illustrates the usage of this function in a very simple program that connects the ESP32 to a WiFi network and starts the SNTP synchronization.

#include <WiFi.h>

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPassword";

void setup()
{
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.println("Connecting...");
  }
  
  Serial.println("Connected with success");

  configTime(0, 3600, "time.google.com");

  Serial.print("Sync time in ms: ");
  Serial.println(sntp_get_sync_interval());
}

void loop()
{
}

Upon running the previous code, we should get a result similar to figure 1. As can be seen, we have obtained a value of 3600000 milliseconds (the default value). This value is coherent with what we mentioned in the previous tutorial: the sync interval is defined by the CONFIG_LWIP_SNTP_UPDATE_DELAY configuration, which defaults to 3600000 milliseconds (1 hour) in the Arduino core (this configuration can be seen here).

SNTP sync default interval for the ESP32.
Figure 1 – SNTP sync default interval for the ESP32.

Nonetheless, it is possible to change this value, as we will see in the section below.

Setting the SNTP sync interval

In this section we are going to learn how to set the SNTP sync interval. This might be useful, for example, if the clock deviation between two SNTP syncs with the default interval cannot be tolerated for a given application, but a lower sync interval makes the deviation acceptable.

Note however that the SNTPv4 RFC 4330 enforces a minimum sync interval of 15 seconds, meaning that any value lower than this will be converted to 15 seconds [1].

The function we need to call to set this interval is the sntp_set_sync_interval. This function receives as input the interval, in milliseconds, and returns void.

One important note is that, if the SNTP functionality is already operating, to apply the new sync interval we need to call the sntp_restart function. Otherwise, it will only be applied after the last interval is expired [1].

In our case we will call this function before calling the Arduino configTime function, so the value we configure should already be the initial interval. We are going to set the minimum possible value: 15 seconds (15000 milliseconds).

But before we jump to that section of the code, we first need to take in consideration that we need to include the esp_sntp.h library, to be able to access the sntp_set_sync_interval function.

#include "esp_sntp.h"

Then we will do the call to the sntp_set_sync_interval function inside the Arduino setup. We will also do a call to the sntp_get_sync_interval function after and before, to confirm the expected value was set.

Only then, we will initialize the SNTP functionality and set the timezone with the already mentioned call to the configTime function.

Serial.print("Sync time in ms before set: ");
Serial.println(sntp_get_sync_interval());

sntp_set_sync_interval(15000);

Serial.print("Sync time in ms after set: ");
Serial.println(sntp_get_sync_interval());

configTime(0, 3600, "time.google.com");

The complete code is shown below.

#include <WiFi.h>
#include "esp_sntp.h"

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPass";

void setup()
{
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.println("Connecting...");
  }
  
  Serial.println("Connected with success");

  Serial.print("Sync time in ms before set: ");
  Serial.println(sntp_get_sync_interval());

  sntp_set_sync_interval(15000);

  Serial.print("Sync time in ms after set: ");
  Serial.println(sntp_get_sync_interval());

  configTime(0, 3600, "time.google.com");
}

void loop()
{
}

Once again, after compiling and uploading the code, we can see the result in the Arduino IDE serial monitor, like shown on figure 2. We can see that the value was originally set to 3600000 milliseconds (default) and was then updated to 15000 milliseconds, as we defined in the code.

ESP32 SNTP update interval before and after being set with new value.
Figure 2 – SNTP update interval before and after being set with new value.

Setting a SNTP time sync callback

To finish our tutorial, we are going to analyze how to configure a callback function that will be executed when a SNTP synchronization happens. This may have many usages but, in our case, it will allow us to confirm that syncs are happening at the rate we defined.

Like in the examples before, we are going to start by the library includes, following by the declaration of the credentials of the WiFi network to which we want to connect the ESP32.

#include <WiFi.h>
#include "esp_sntp.h"

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPass";

On the Arduino setup, we take care of opening a serial connection, to output the results of our program, and then we connect the ESP32 to the WiFi network, so the SNTP synchronization can later happen.

Serial.begin(115200);
  
WiFi.begin(ssid, password);
  
while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.println("Connecting...");
}
  
Serial.println("Connected with success");

After this, we will register a callback function, which will be executed every time a sync occurs. We do this with a call to the sntp_set_time_sync_notification_cb function, passing as input the callback function.

We will name the callback function timeSyncCallback and define it later.

sntp_set_time_sync_notification_cb(timeSyncCallback);

After this we will set the sync interval to 20 seconds, like before calling the sntp_set_sync_interval function.

sntp_set_sync_interval(20000);

Finally, we call the configTime function from the Arduino core, to set the time zone and the SNTP server.

configTime(0, 3600, "time.google.com");

The complete setup function is summarized below.

void setup()
{
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.println("Connecting...");
  }
  
  Serial.println("Connected with success");

  sntp_set_time_sync_notification_cb(timeSyncCallback);

  sntp_set_sync_interval(20000);
  configTime(0, 3600, "time.google.com");
}

To finish the code, we will analyze the timeSyncCallback function. This function should have a header accordingly to this definition. In short, the function should return void and receive as input a pointer to a struct of type timeval, which will contain the time received from the SNTP server.

void timeSyncCallback(struct timeval *tv)
{
    // Function implementation
}

This struct has two fields:

  • tv_sec: Number of whole seconds of elapsed time, as time_t [2], since the start of the epoch.
  • tv_usec: Number of microseconds of the rest of elapsed time minus tv_sec [2].

So, we will first print the number of elapsed seconds to the serial port.

Serial.println(tv->tv_sec);

Then we will use the ctime function to convert the elapsed seconds value to a local calendar time string. This function receives as input a pointer to the time_t struct we want to convert and returns as output a string.

Serial.println(ctime(&tv->tv_sec));

The whole code is shown below.

#include <WiFi.h>
#include "esp_sntp.h"

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPass";

void timeSyncCallback(struct timeval *tv)
{
  Serial.println("\n----Time Sync-----");
  Serial.println(tv->tv_sec);
  Serial.println(ctime(&tv->tv_sec));
}

void setup()
{
  Serial.begin(115200);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.println("Connecting...");
  }
  
  Serial.println("Connected with success");

  sntp_set_time_sync_notification_cb(timeSyncCallback);

  sntp_set_sync_interval(20000);
  configTime(0, 3600, "time.google.com");
}

void loop()
{
}

Upon running the code, we should get something similar to figure 3. As can be seen, we get periodic executions of our callback function, with the expected interval of 20 seconds between them.

Output of the program, showing the prints from the SNTP sync callback function executions.
Figure 3 – Output of the program, showing the prints from the SNTP sync callback function executions.

Suggested Readings

References

[1] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/system_time.html#_CPPv422sntp_set_sync_interval8uint32_t

[2] https://renenyffenegger.ch/notes/development/languages/C-C-plus-plus/C/libc/structs/timeval

Leave a Reply