5. Adding an accurate Timestamp with NTP (Arduino) - ControlBits/EMIT GitHub Wiki

Having good quality environmental data is important for many applications, but in order to use this data to help us spot trends over time, we need to understand when each of our data points were measured - we need to add an accurate 'Timestamp' to each of our measurements.

As we now we have an Internet connection, we can use the 'Network Time Protocol' (NTP) to get accurate date and time information. From wikipedia.com:

The Network Time Protocol (NTP) is a networking protocol for clock synchronization between computer systems over packet-switched, variable-latency data networks. In operation since before 1985, NTP is one of the oldest Internet protocols in current use.

NTP is intended to synchronize all participating computers to within a few milliseconds of Coordinated Universal Time (UTC).

Source: https://en.wikipedia.org/wiki/Network_Time_Protocol

EMIT's ESP32's already has an onboard clock, so all we need to do is synchronise our 'local time' with the NTP server network to ensure it's accurate.

Once that has been done, we can safely rely on our local time to create a 'Timestamp' for each of our sensor readings.

5.1 Synchronising local time to NTP network

The first thing we need to do is include the 'time' library by adding the following line to the include section of the Sketch.

#include "time.h"

so the include section of the sketch will look like this:

// WiFi libary is essential for connecting to WIFI
#include <WiFi.h>
// time library sync EMIT ESP32 clock with a NTP server
#include "time.h"

Next up, we need to call a function in setup(), that will check to see if we are connected to the NTP servers and if so, synchronise our local time to the NTP network. All this is done, with a single statement:

configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

But this function takes three arguments that are not defined yet, that represents the timezone, the daylight offset and the NTP server we are synchronizing to. we will define them as global variable on the definition section of the sketch.

// we will request the time from pool.ntp.org
const char* ntpServer = "pool.ntp.org";
// The gmtOffset_sec variable defines the offset in seconds between your time zone and GMT
const long  gmtOffset_sec = 0;
// defines the offset in seconds for daylight saving time. It is generally one hour, that corresponds to 3600 seconds
const int   daylightOffset_sec = 3600;

We just need to synchronise the time with the NTP server once during the setup() function and then the time will be updated internally without needing to frequently update with the server.

Next we need to define a handy function that simply gets the 'local time' and converts it into a formatted string that we can use as our Timestamp. The function will return a pointer to a static defined string the resides in memory throughout the life of the code. The function definition looks like this:

char* get_time_str(void)

The static defined string is a character array of a fixed length of 60.

  static char my_time_string[60];

The time library in the ESP32arduino core provides a function that reads the current time into a structure called 'tm'. which consisting of each of clock parameters like seconds, minutes, hours, day of the month, month of the year, number of years from 1900 and other parameters not in this tutorial scope.

First we will define the 'tm' structure:

struct tm my_time;

Then we will call the getLocalTime which return 0 on failure so we will catch this failure printing a fault statement on the serial monitor.

  if(!getLocalTime(&my_time)){
    Serial.println("Failed to obtain time");
    return NULL;
  }

Then we will copy the clock parameters from 'tm' into the formatted time string:

  snprintf(my_time_string, // string to add the formated time to.
           60, // string length to prevent overflow
           "%04d-%02d-%02d %02d:%02d:%02d", // time string format
           (my_time.tm_year + 1900),
           (my_time.tm_mon  + 1),
            my_time.tm_mday,
            my_time.tm_hour,my_time.tm_min,my_time.tm_sec);

The fully commented 'get_time_str()' function is shown below:

// define function for getting local time as string
// returns a pointer to a string containing the formatted time.
char* get_time_str(void){
  // string holding well formatted time.
  static char my_time_string[60];
  // structure holding the time
  // definition is as follow
  //     struct tm
  //    {
  //      int tm_sec;  // seconds from 0 to 59
  //      int tm_min;  // minutes from 0 to 59
  //      int tm_hour; // hours from 0 to 23
  //      int tm_mday; // day of the month from 1 to 30
  //      int tm_mon;  // month of the year from 0 to 11
  //      int tm_year; // number of years from 1900
  //      ... other variables not in the scope of the tutorial..
  //    };
  struct tm my_time;
  // read the time into timeinfo structure.
  if(!getLocalTime(&my_time)){
    Serial.println("Failed to obtain time");
    return NULL;
  }
  snprintf(my_time_string, // string to add the formated time to.
           60, // string length to prevent overflow
           "%04d-%02d-%02d %02d:%02d:%02d", // time string format
           (my_time.tm_year + 1900),
           (my_time.tm_mon  + 1),
            my_time.tm_mday,
            my_time.tm_hour,my_time.tm_min,my_time.tm_sec);

  // return the formated time string
  return my_time_string;     
}

5.2 Using our new, accurate localtime to Timestamp our data

Now we've created the NTP functions, all we need to do is 1) connect to the Wi-Fi network (already in our code), 2) wait for a few seconds for the network connection to settle and then 3) run the ESP32 time module 'configTime' function.

  // attempt a wifi connection
  wifi_connect();

  // wait 5 seconds ( 5000 milliseconds ) for network to settle
  delay(5000);
  
  // synchronise time with the NTP server.
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

Once that is done, we're ready to use our accurate 'localtime' whenever we need to, just by calling our 'get_time_str()' function.

To add the Timestamp to our measurement output, we just need to add the following print statement to our code:

  Serial.print("Time Stamp: ");
  Serial.println(get_time_str());

Our completed Sketch should look like this:

// WiFi libary is essential for connecting to WIFI
#include <WiFi.h>
// time library sync EMIT ESP32 clock with a NTP server
#include "time.h"
// include the Adafruit DHT libraries to use its functions reading the temperature and humidity from the DHT22 sensor.
#include "DHT.h"


#define EMIT_RED_LED 16
#define EMIT_GREEN_LED 17


#define EMIT_DHT_PIN 14     // Digital pin connected to the DHT sensor

#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

// Initialize DHT sensor.
DHT dht(EMIT_DHT_PIN, DHTTYPE);

// define Wi-Fi settings
#define WIFISSID "your-WiFi-SSID-goes-here"
#define WIFIPASSWORD "your-WiFi-password-goes-here"

// we will request the time from pool.ntp.org
const char* ntpServer = "pool.ntp.org";
// The gmtOffset_sec variable defines the offset in seconds between your time zone and GMT
const long  gmtOffset_sec = 0;
// defines the offset in seconds for daylight saving time. It is generally one hour, that corresponds to 3600 seconds
const int   daylightOffset_sec = 3600;


void wifi_connect(void){
  // change the wifi interface mode to station 
  WiFi.mode(WIFI_STA);

  // attempt a WiFi connection to the router with credentials of WIFISSID and WIFIPASSWORD
  WiFi.begin(WIFISSID, WIFIPASSWORD);

  // wait for the connection to be established.
  while (WiFi.status() != WL_CONNECTED){
      // flash the Green LED indication a wifi connection attempt.
      // turn on the Green LED 
      digitalWrite(EMIT_GREEN_LED , HIGH);
      // print 'trying..' message to Serial Monitor 
      Serial.print("trying WiFi connection ");
      Serial.println(WIFISSID);  

      // wait 1 second (1000 milliseconds)
      delay(1000);

      // turn off the Green LED 
      digitalWrite(EMIT_GREEN_LED , LOW);

      // wait 2 second (2000 milliseconds)
      delay(2000);      
  }
  // if connected ...
  // turn on the Green LED 
  digitalWrite(EMIT_GREEN_LED , HIGH);

  // print 'WiFi connection successful' message to Serial Monitor 
  Serial.println("WiFi connection successful") ; 

  // print WiFi network settings (inc. IP Address) to Serial Monitor. 
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
 
}
// define function for getting local time as string
// returns a pointer to a string containing the formated time.
char* get_time_str(void){
  // string holding well formated time.
  static char my_time_string[60];
  // structure holding the time
  // defination is as follow
  //     struct tm
  //    {
  //      int tm_sec;  // seconds from 0 to 59
  //      int tm_min;  // minutes from 0 to 59
  //      int tm_hour; // hours from 0 to 23
  //      int tm_mday; // day of the month from 1 to 30
  //      int tm_mon;  // month of the year from 0 to 11
  //      int tm_year; // number of years from 1900
  //      ... other variables not in the scope of the tutorial..
  //    };
  struct tm my_time;
  // read the time into timeinfo structure.
  if(!getLocalTime(&my_time)){
    Serial.println("Failed to obtain time");
    return NULL;
  }
  snprintf(my_time_string, // string to add the formated time to.
           60, // string length to prevent overflow
           "%04d-%02d-%02d %02d:%02d:%02d", // time string format
           (my_time.tm_year + 1900),
           (my_time.tm_mon  + 1),
            my_time.tm_mday,
            my_time.tm_hour,my_time.tm_min,my_time.tm_sec);

  // return the formated time string
  return my_time_string;     
}
void setup() {
  // Enable the serial terminal
  // Useful for debugging messages
  Serial.begin(115200);  
  // configure RED LED GPIO to be OUTPUT
  // so that we would control the LED (ON , OFF)
  pinMode(EMIT_RED_LED , OUTPUT);
  
  // on the begining we will turn OFF the RED LED 
  digitalWrite(EMIT_RED_LED , LOW);

  // configure Green LED GPIO to be OUTPUT
  // so that we would control the LED (ON , OFF)
  pinMode(EMIT_GREEN_LED , OUTPUT);
  
  // on the begining we will turn OFF the GREEN LED 
  digitalWrite(EMIT_GREEN_LED , LOW);
  
  // initialize the DHT communication
  dht.begin();

  // attempt a wifi connection
  wifi_connect();

  // wait 5 seconds ( 5000 milliseconds ) for network to settle
  delay(5000);
  
  // synchronise time with the NTP server.
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  
}

void loop() {
  //Wait for 5 seconds ( 5000 milliseconds )
  delay(5000);

  // turn the red LED onma
  digitalWrite(EMIT_RED_LED , HIGH);

  // Print a debugging message on the Serial Terminal indicating the start of the read attempt.
  Serial.println("Reading AM2302 ...");
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 
  float humidity = dht.readHumidity();
  // Read temperature as Celsius (the default)
  float tempC = dht.readTemperature();
  // Read temperature as Fahrenheit (isFahrenheit = true)
  float tempF = dht.readTemperature(true);
  
  // turn the red LED off indicating the end of the read cycle
  digitalWrite(EMIT_RED_LED , LOW);

  // Print the read values on the Serial Monitor.
  Serial.print("Time Stamp: ");
  Serial.println(get_time_str());
  Serial.print("Temperature (C): ");
  Serial.println(tempC);
  Serial.print("Temperature (F): ");
  Serial.println(tempF);  
  Serial.print("Humidity (%RH): ");
  Serial.println(humidity);

}

As the code that we've created is the foundation of your IoT sensor code, we strongly recommend you upload and test what we've created so far ... if everything is okay, the serial monitor should contain some timestamped reading as shown below:

timestamped_reading

Now it's the time to send our data to the internet as a thing. this would lead us toon to the next section: Transmitting data over MQTT

⚠️ **GitHub.com Fallback** ⚠️