Chapter 98: SNTP and Time Synchronization
Chapter Objectives
After completing this chapter, you will be able to:
- Understand the importance of accurate time synchronization in networked embedded systems.
- Explain the basics of Network Time Protocol (NTP) and Simple Network Time Protocol (SNTP).
- Describe how SNTP clients synchronize time with servers.
- Configure and use the ESP-IDF
esp_sntp
component to synchronize the ESP32’s system time. - Handle time synchronization events.
- Set and retrieve the system time on the ESP32.
- Understand and apply timezone and Daylight Saving Time (DST) concepts.
- Format and display time information in human-readable strings.
- Identify common issues and troubleshoot SNTP configurations.
Introduction
In the interconnected world of IoT devices, accurate timekeeping is often not just a convenience but a fundamental requirement. Whether it’s for timestamping sensor data, scheduling tasks, managing secure communications (TLS certificates have validity periods), or synchronizing actions across multiple devices, knowing the correct time is crucial. While ESP32 devices have a Real-Time Clock (RTC), its accuracy can drift over time and it doesn’t inherently know the current wall-clock time after a power cycle without external input.
The Simple Network Time Protocol (SNTP) provides a straightforward way for devices connected to an IP network to synchronize their internal clocks with accurate time sources available on the internet or a local network. This chapter will delve into the principles of SNTP and demonstrate how to leverage the ESP-IDF’s SNTP client to ensure your ESP32 applications operate with precise and reliable time information.
Theory
What is Time Synchronization and Why is it Important?
Time synchronization is the process of setting the internal clock of a device to match a standard time reference, usually Coordinated Universal Time (UTC). For an embedded system like the ESP32, this means its software can access a consistent and accurate timestamp.
Area of Importance | Reason | ESP32 Example Scenario |
---|---|---|
Data Logging & Analytics | Ensures correct sequencing, correlation, and analysis of events or sensor readings. | An ESP32 environmental sensor logs temperature/humidity with accurate timestamps for trend analysis. |
Task Scheduling | Enables precise execution of tasks at specific times or regular intervals. | An ESP32-based irrigation system watering plants at scheduled times of the day. |
Security (TLS/SSL) | Certificate validation relies on the system clock being within the certificate’s validity period. | ESP32 making a secure HTTPS request or WSS connection; incorrect time can cause handshake failure. |
Network Coordination | Some distributed systems or protocols require synchronized clocks for operations like authentication or sequencing. | Multiple ESP32s coordinating actions in a distributed sensing network based on synchronized event times. |
User Interface & Experience | Displaying accurate local time to users. | An ESP32 with a display showing the correct current time and date to the user. |
Debugging & Forensics | Accurate timestamps in logs are crucial for diagnosing issues and reconstructing event sequences. | Analyzing ESP32 crash logs or event logs to understand the timeline leading to an error. |
File Systems | Correctly timestamping file creation and modification times. | ESP32 saving data files to an SD card with accurate timestamps. |
Importance in Embedded Systems:
- Data Logging: Accurate timestamps are essential for logging sensor readings, events, or errors. This allows for correct sequencing and analysis of data, especially when correlating information from multiple sources or over long periods.
- Scheduling: Many applications require tasks to be performed at specific times or intervals (e.g., taking a sensor reading every hour, rebooting at midnight).
- Security: TLS/SSL certificates, which are fundamental for secure communication (HTTPS, WSS, MQTTS), have validity periods defined by start and end dates/times. An incorrect system clock can lead to certificate validation failures, preventing secure connections.
- Network Operations: Some network protocols and distributed systems rely on synchronized clocks for proper operation, coordination, or authentication (e.g., Kerberos).
- User Interface: Displaying the correct local time to users is often a requirement for devices with user interfaces.
- Debugging and Forensics: Accurate logs with correct timestamps are invaluable for diagnosing issues and understanding system behavior after an event.
- File Systems: Filesystems use timestamps for file creation and modification times.
Without synchronization, a device’s clock might start from a default value (like the Unix epoch, January 1, 1970) on each boot-up, or an uncalibrated RTC might drift significantly.
Introduction to NTP and SNTP
Network Time Protocol (NTP):
NTP is a robust and complex networking protocol designed to synchronize the clocks of computer systems over packet-switched, variable-latency data networks. It uses a hierarchical system of time sources, with stratum levels indicating the distance from a primary reference clock (e.g., an atomic clock or GPS receiver). NTP clients can synchronize with multiple servers, employing sophisticated algorithms to filter out network jitter and select the most accurate time sources.
Simple Network Time Protocol (SNTP):
SNTP is a simplified version of NTP, defined in RFC 4330 (for SNTPv4). It is designed for applications where the full complexity of an NTP implementation is not required or feasible, such as in embedded systems or simple clients. SNTP typically involves a client sending a request to a designated NTP server and using the server’s response to adjust its local clock. It usually doesn’t perform the complex filtering or multiple server polling of a full NTP client.
Feature | NTP (Network Time Protocol) | SNTP (Simple Network Time Protocol) |
---|---|---|
Complexity | High; complex algorithms, state machine. | Low; simplified version, stateless client typical. |
Accuracy | Very high (sub-millisecond typically possible on LANs). | Sufficient for most applications (milliseconds to seconds, depending on network). |
Resource Usage | Higher (CPU, memory, network bandwidth). | Lower; suitable for resource-constrained devices like MCUs. |
Server Interaction | Can synchronize with multiple servers, complex filtering and server selection algorithms. | Typically interacts with one or a few pre-configured servers. Simpler client-server exchange. |
Use Cases | Servers, workstations, systems requiring highest precision. | Embedded systems, IoT devices, simple clients where moderate accuracy is acceptable. |
ESP-IDF Component | No full NTP client in ESP-IDF. | esp_sntp component provided. |
For most ESP32 applications, SNTP provides a sufficient level of accuracy and is much less resource-intensive than a full NTP implementation. The ESP-IDF provides an SNTP client component.
Key Concepts:
- UTC (Coordinated Universal Time): The primary time standard by which the world regulates clocks and time. It is one of several closely related successors to Greenwich Mean Time (GMT).
- Epoch: A reference point in time from which time is measured. For many Unix-like systems (and thus often in C libraries), the epoch is January 1, 1970, at 00:00:00 UTC. Time is often represented as the number of seconds or milliseconds elapsed since this epoch.
- NTP/SNTP Server: A server connected to an accurate time source (e.g., a higher stratum NTP server, GPS, or atomic clock) that provides time information to clients. Public NTP servers are widely available (e.g.,
pool.ntp.org
).
SNTP Operation
The basic SNTP client-server interaction is as follows:
- Client Request: The SNTP client (e.g., ESP32) sends an SNTP message (a UDP packet) to a preconfigured NTP server on UDP port 123. This packet contains a timestamp (
Transmit Timestamp
) indicating when the client sent the request according to its own clock. - Server Processing: The NTP server receives the request. It records its own time when the packet arrived (
Receive Timestamp
) and when it sends the response (Transmit Timestamp
). - Server Response: The server sends an SNTP message back to the client. This response includes:
Originate Timestamp
: The client’s originalTransmit Timestamp
(copied from the request).Receive Timestamp
: The time the server received the request (according to the server’s clock).Transmit Timestamp
: The time the server sent the response (according to the server’s clock).
- Client Calculation: The client receives the server’s response. It records its own time of arrival (
Destination Timestamp
). Using these four timestamps (Originate, Receive, Transmit from server, and Destination), the client can estimate:- Round-trip delay:
(Destination Timestamp - Originate Timestamp) - (Server Transmit Timestamp - Server Receive Timestamp)
- Clock offset: ((Server Receive Timestamp – Originate Timestamp) + (Server Transmit Timestamp – Destination Timestamp)) / 2The client then uses this offset to adjust its local clock.
- Round-trip delay:
%%{init: {"sequence": {"showSequenceNumbers": true, "messageFontFamily": "\"Open Sans\", sans-serif"} } }%% sequenceDiagram participant Client participant SNTP Server Client->>+SNTP Server: SNTP Request (UDP Port 123)<br>Contains: Client's Transmit Timestamp (T1) Note right of Client: T1: Client's clock time at send SNTP Server->>SNTP Server: Process Request Note left of SNTP Server: T2: Server's clock time at receive<br>T3: Server's clock time at send response SNTP Server-->>-Client: SNTP Response<br>Contains:<br>- Originate Timestamp (T1 from client req)<br>- Server Receive Timestamp (T2)<br>- Server Transmit Timestamp (T3) Note right of Client: T4: Client's clock time<br> at receive response Note over Client: Calculations by Client: Note over Client: Round-trip Delay (d) = (T4 - T1) - (T3 - T2) Note over Client: Clock Offset (o) = ((T2 - T1) + (T3 - T4)) / 2 Note over Client: Client adjusts its local clock using 'o'.
The ESP-IDF esp_sntp
component handles these calculations internally.
Timezones and Daylight Saving Time (DST)
SNTP servers provide time in UTC. To display or use local time, an application needs to account for:
- Timezone: A geographical region that observes a uniform standard time for legal, commercial, and social purposes. Timezones are typically defined as offsets from UTC (e.g., EST is UTC-5, CET is UTC+1).
- Daylight Saving Time (DST): A practice of advancing clocks during warmer months so that darkness falls at a later clock time. When DST is in effect, the local time offset from UTC changes (e.g., EDT is UTC-4).
Managing timezones and DST can be complex because:
- DST rules vary by region and can change over time due to legislation.
- The exact start and end dates/times for DST are not fixed globally.
The C standard library provides functions (like localtime_r
, mktime
) that can help convert UTC time (e.g., time_t
) to local time structures (struct tm
), provided the system’s locale and timezone information is correctly configured. This is typically done by setting the TZ
environment variable.
TZ Environment Variable:
Timezone / Region | Example TZ String | Brief Explanation |
---|---|---|
UTC (Coordinated Universal Time) | UTC0 or GMT0 |
No offset from UTC, no Daylight Saving Time. |
US Eastern Time (New York) | EST5EDT,M3.2.0/2,M11.1.0/2 |
EST (UTC-5), EDT for DST. DST starts 2nd Sunday in March at 2 AM, ends 1st Sunday in November at 2 AM. |
US Pacific Time (Los Angeles) | PST8PDT,M3.2.0/2,M11.1.0/2 |
PST (UTC-8), PDT for DST. Same DST rules as Eastern. |
UK (London) | GMT0BST,M3.5.0/1,M10.5.0/2 |
GMT (UTC+0), BST (UTC+1) for DST. DST starts last Sunday in March at 1 AM, ends last Sunday in October at 2 AM. |
Central European Time (Berlin, Paris) | CET-1CEST,M3.5.0/2,M10.5.0/3 |
CET (UTC+1), CEST (UTC+2) for DST. DST starts last Sunday in March at 2 AM, ends last Sunday in October at 3 AM. |
India Standard Time (IST) | IST-5:30 |
IST (UTC+5:30). No Daylight Saving Time. |
Japan Standard Time (JST) | JST-9 |
JST (UTC+9). No Daylight Saving Time. |
Australia Eastern Standard Time (Sydney – with DST) | AEST-10AEDT,M10.1.0/2,M4.1.0/3 |
AEST (UTC+10), AEDT (UTC+11) for DST. DST starts 1st Sunday in Oct at 2 AM, ends 1st Sunday in April at 3 AM. (Rules can vary by state). |
Note: TZ string rules, especially for DST, can be complex and may change. Always verify the correct string for a specific region and current regulations. The format is generally STDoffset[DST[offset],start[/time],end[/time]]
.
The TZ environment variable is a standard way to define timezone and DST rules. Its format can be complex but often includes:
- Standard timezone abbreviation (e.g.,
EST
). - Offset from UTC (e.g.,
5
for EST, meaning UTC-5). - DST timezone abbreviation (e.g.,
EDT
). - Optionally, the offset for DST if different from standard time by one hour.
- Optionally, rules for when DST starts and ends.
Example TZ
string for US Eastern Time: EST5EDT,M3.2.0/2,M11.1.0/2
EST5EDT
: Standard time is EST, offset 5 hours west of UTC. DST is EDT.M3.2.0/2
: DST starts on the second Sunday (2) of March (3) at 2 AM.M11.1.0/2
: DST ends on the first Sunday (1) of November (11) at 2 AM.
Setting the TZ
variable correctly allows C library functions to perform accurate local time conversions.
ESP-IDF esp_sntp
API
The ESP-IDF provides the esp_sntp
component (header: esp_sntp.h
) for SNTP client functionality.
Key Functions and Configuration:
Function Name | Brief Description | Common Usage |
---|---|---|
esp_sntp_setoperatingmode(mode) |
Sets the SNTP operating mode. | Usually set to ESP_SNTP_OPMODE_POLL for periodic server polling. |
esp_sntp_setservername(index, server_name) |
Configures an NTP server by hostname or IP address. | Call for each server (index 0, 1, etc.). E.g., esp_sntp_setservername(0, "pool.ntp.org") . |
esp_sntp_set_time_sync_notification_cb(callback) |
Registers a callback function invoked upon time synchronization. | Essential for knowing when sync is complete and for actions like setting TZ. Callback receives struct timeval *tv . |
esp_sntp_init() |
Initializes and starts the SNTP service. | Called after configuration and Wi-Fi connection. |
esp_sntp_stop() |
Stops the SNTP service. | Called when SNTP is no longer needed or before deep sleep. |
esp_sntp_get_sync_status() |
Returns the current synchronization status. | Check if status is SNTP_SYNC_STATUS_COMPLETED , SNTP_SYNC_STATUS_IN_PROGRESS , or SNTP_SYNC_STATUS_RESET . |
esp_sntp_set_sync_mode(sync_mode) |
Sets the time synchronization mode. | SNTP_SYNC_MODE_IMMED (default, immediate jump) or SNTP_SYNC_MODE_SMOOTH (gradual adjustment). |
esp_sntp_enabled() |
Checks if SNTP service is currently enabled/running. | Can be used to verify if esp_sntp_init() was successful and esp_sntp_stop() hasn’t been called. |
- Initialization and Configuration:
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL)
: Sets the operating mode.ESP_SNTP_OPMODE_POLL
is typical, where the client polls the server periodically.ESP_SNTP_OPMODE_LISTENONLY
is also possible.esp_sntp_setservername(0, "pool.ntp.org")
: Configures the NTP server hostname or IP address. You can configure multiple servers by changing the index (0, 1, etc.).esp_sntp_set_time_sync_notification_cb(time_sync_notification_cb_t callback)
: Registers a callback function that is invoked when time synchronization is completed (or fails).- The callback receives a
struct timeval *tv
argument containing the synchronized time.
- The callback receives a
esp_sntp_init()
: Initializes and starts the SNTP service.
- Stopping SNTP:
esp_sntp_stop()
: Stops the SNTP service.
- Getting Sync Status:
esp_sntp_get_sync_status()
: Returns the current synchronization status (SNTP_SYNC_STATUS_RESET
,SNTP_SYNC_STATUS_COMPLETED
,SNTP_SYNC_STATUS_IN_PROGRESS
).esp_sntp_get_sync_mode()
: Returns the current sync mode (SNTP_SYNC_MODE_IMMED
orSNTP_SYNC_MODE_SMOOTH
).IMMED
sets time immediately,SMOOTH
gradually adjusts. Default isIMMED
.esp_sntp_set_sync_mode(sntp_sync_mode_t sync_mode)
: Sets the sync mode.
- Time Structure (struct timeval):Used by the notification callback and for setting system time.
struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ };
Wheretime_t
is typically along int
representing seconds since the epoch. - System Time Functions (from sys/time.h and time.h):Once SNTP has synchronized and the system clock is set (often done automatically by the SNTP service or in its callback):
settimeofday(const struct timeval *tv, const struct timezone *tz)
: Sets the system’s date and time. Thetz
argument is often unused/obsolete on many systems; timezone is handled viaTZ
.gettimeofday(struct timeval *tv, struct timezone *tz)
: Gets the current system time.time(time_t *tloc)
: Gets current calendar time astime_t
(seconds since epoch).localtime_r(const time_t *timep, struct tm *result)
: Convertstime_t
tostruct tm
representing local time, considering theTZ
environment variable. Thread-safe version oflocaltime
.gmtime_r(const time_t *timep, struct tm *result)
: Convertstime_t
tostruct tm
representing UTC time. Thread-safe.strftime(char *s, size_t max, const char *format, const struct tm *tm)
: Formats astruct tm
into a human-readable string according toformat
.setenv("TZ", "YourTZString", 1)
: Sets theTZ
environment variable.tzset()
: Initializes timezone data from theTZ
environment variable. Must be called after settingTZ
and before calls likelocaltime_r
.
Function (Header) | Signature | Description |
---|---|---|
settimeofday (sys/time.h) |
int settimeofday(const struct timeval *tv, const struct timezone *tz); |
Sets the system’s current time. tv provides seconds and microseconds. tz is usually NULL. Often called by SNTP service itself. |
gettimeofday (sys/time.h) |
int gettimeofday(struct timeval *tv, struct timezone *tz); |
Gets the system’s current time, populating tv . tz is usually NULL. |
time (time.h) |
time_t time(time_t *tloc); |
Gets current calendar time as time_t (seconds since epoch). If tloc is not NULL, the value is also stored there. |
setenv (stdlib.h) |
int setenv(const char *name, const char *value, int overwrite); |
Sets an environment variable. Used to set “TZ” for timezone information. |
tzset (time.h) |
void tzset(void); |
Initializes timezone data from the TZ environment variable. Call after setting “TZ”. |
localtime_r (time.h) |
struct tm *localtime_r(const time_t *timep, struct tm *result); |
Converts time_t to a struct tm representing local time, using TZ setting. Thread-safe. |
gmtime_r (time.h) |
struct tm *gmtime_r(const time_t *timep, struct tm *result); |
Converts time_t to a struct tm representing UTC time. Thread-safe. |
strftime (time.h) |
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); |
Formats a struct tm into a human-readable string according to the format string. |
ctime (time.h) |
char *ctime(const time_t *timep); |
Converts time_t to a string like “Wed Jun 30 21:49:08 1993\n”. Not thread-safe; use ctime_r or strftime for safer alternatives. |
Typical SNTP Workflow:
%%{init: {"flowchart": {"htmlLabels": true, "fontFamily": "\"Open Sans\", sans-serif"} } }%% graph TD A[Start: Application Boot] --> B{Connect to Wi-Fi &<br>Obtain IP Address}; B -- Success --> C["Configure SNTP Servers<br><tt>esp_sntp_setservername()</tt>"]; B -- Failure --> BError["Error: No Network<br>Cannot proceed with SNTP"]; C --> D["Set SNTP Operating Mode<br><tt>esp_sntp_setoperatingmode()</tt>"]; D --> E["Register Time Sync Notification Callback<br><tt>esp_sntp_set_time_sync_notification_cb()</tt>"]; E --> F["Initialize & Start SNTP Service<br><tt>esp_sntp_init()</tt>"]; F --> G{"Waiting for SNTP Synchronization...<br>(Background Process)"}; G -- Sync Complete --> H["<b>time_sync_notification_cb()</b> Invoked<br>Receives <tt>struct timeval *tv</tt>"]; H --> I["System Time is Set (usually by SNTP service)<br>If not, call <tt>settimeofday(tv, NULL)</tt>"]; I --> J["Set Timezone Environment Variable<br><tt>setenv(\[dq]TZ\[dq], \[dq]YourTZString\[dq], 1)</tt> dq: double quote"]; J --> K["Apply Timezone Setting<br><tt>tzset()</tt>"]; K --> L[System Time Synchronized & Localized]; L --> M["Use Time Functions:<br><tt>time()</tt>, <tt>gettimeofday()</tt><br><tt>localtime_r()</tt>, <tt>strftime()</tt>"]; M --> N{Application Logic Requiring Time}; G -- Sync Timeout / Error --> GError["Error: SNTP Sync Failed<br>(Check network, server, logs)"]; subgraph Post_Sync_Operations ["Post-Synchronization Operations"] direction LR J K L M N end classDef startEnd fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6; classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF; classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E; classDef callback fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46; classDef error fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B; class A,L startEnd; class B,C,D,E,F,G,I,J,K,M,N process; class H callback; class BError,GError error;
- Ensure Wi-Fi is connected and an IP address is obtained.
- Configure SNTP server(s).
- Register the time synchronization notification callback.
- Initialize SNTP service (
esp_sntp_init()
). - Wait for the callback to be invoked, indicating time has been synchronized.
- Inside the callback, the system time is typically set using
settimeofday()
. Theesp_sntp
component might do this automatically if configured or by default. - Set the
TZ
environment variable and calltzset()
. - Use
time()
,localtime_r()
, andstrftime()
to work with and display local time.
Practical Examples
Before running these examples, ensure your ESP32 is configured to connect to a Wi-Fi network.
Example 1: Basic SNTP Time Synchronization and Callback
This example initializes SNTP, sets a server, and uses a callback to report when time is synchronized.
1. Project Setup and CMakeLists.txt:
In your main component’s CMakeLists.txt:
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES esp_wifi esp_event esp_log nvs_flash netif_stack esp_netif esp_sntp lwip)
# lwip might be needed for sntp functions or types, or included transitively
2. main.c
Implementation:
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_sntp.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// Define your Wi-Fi credentials
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
static const char *TAG = "SNTP_EXAMPLE";
static SemaphoreHandle_t wifi_connected_sem;
// SNTP Time Sync Notification Callback
void time_sync_notification_cb(struct timeval *tv) {
ESP_LOGI(TAG, "Time synchronization event received!");
ESP_LOGI(TAG, "Time successfully synchronized: %lld seconds, %ld microseconds since epoch",
(long long)tv->tv_sec, (long)tv->tv_usec);
// The system time is now set. We can try to get and print it.
// Note: settimeofday is usually called by the SNTP service itself.
// If not, you would call it here: settimeofday(tv, NULL);
// Example: Get current time and print it as a basic string
time_t now;
char strftime_buf[64];
struct tm timeinfo;
time(&now); // Get current time (seconds since epoch)
// Set Timezone to UTC for this initial print
// For local time, TZ needs to be set correctly before localtime_r
setenv("TZ", "UTC0", 1);
tzset(); // Apply the TZ environment variable
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "Current time (UTC) after sync: %s", strftime_buf);
}
static void initialize_sntp(void) {
ESP_LOGI(TAG, "Initializing SNTP");
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
// You can use a specific server or a pool
esp_sntp_setservername(0, "pool.ntp.org");
// For multiple servers:
// esp_sntp_setservername(1, "time.google.com");
esp_sntp_set_time_sync_notification_cb(time_sync_notification_cb);
// esp_sntp_set_sync_mode(SNTP_SYNC_MODE_SMOOTH); // Optional: for smooth adjustment
esp_sntp_init();
ESP_LOGI(TAG, "SNTP initialized. Waiting for time synchronization...");
}
// Wi-Fi Event Handler
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
ESP_LOGI(TAG, "Attempting to connect to Wi-Fi...");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Disconnected from Wi-Fi. Retrying...");
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP address: " IPSTR, IP2STR(&event->ip_info.ip));
xSemaphoreGive(wifi_connected_sem); // Signal that Wi-Fi is connected
}
}
// Wi-Fi Initialization
void wifi_init_sta(void) {
wifi_connected_sem = xSemaphoreCreateBinary();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
}
void app_main(void) {
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "SNTP Example Start");
wifi_init_sta();
ESP_LOGI(TAG, "Waiting for Wi-Fi connection...");
if(xSemaphoreTake(wifi_connected_sem, portMAX_DELAY) == pdTRUE) {
ESP_LOGI(TAG, "Wi-Fi Connected. Initializing SNTP.");
initialize_sntp();
} else {
ESP_LOGE(TAG, "Failed to connect to Wi-Fi.");
}
// The SNTP service runs in the background.
// The time_sync_notification_cb will be called upon successful sync.
}
3. Build, Flash, and Observe:
- Replace
"YOUR_WIFI_SSID"
and"YOUR_WIFI_PASSWORD"
with your credentials. - Build, flash, and monitor.
- You should see logs for Wi-Fi connection, then SNTP initialization. After a short while (could be up to a minute or more depending on network and server responsiveness), the
time_sync_notification_cb
should be called, and the synchronized UTC time will be printed.
...
I (SNTP_EXAMPLE): Got IP address: 192.168.1.100
I (SNTP_EXAMPLE): Wi-Fi Connected. Initializing SNTP.
I (SNTP_EXAMPLE): Initializing SNTP
I (SNTP_EXAMPLE): SNTP initialized. Waiting for time synchronization...
...
I (SNTP_EXAMPLE): Time synchronization event received!
I (SNTP_EXAMPLE): Time successfully synchronized: 1678886400 seconds, 123456 microseconds since epoch // Example values
I (SNTP_EXAMPLE): Current time (UTC) after sync: Wed Mar 15 12:00:00 2023 // Example formatted time
...
Tip: The
esp_sntp
service, by default, periodically re-synchronizes the time. The interval can be configured usingCONFIG_LWIP_SNTP_UPDATE_DELAY
insdkconfig
(viamenuconfig
).
Example 2: Displaying Formatted Local Time with Timezone
This example builds upon the first one to set a specific timezone and display the local time.
1. Modify time_sync_notification_cb
and add a display task:
// ... (Keep previous includes, defines, TAG, wifi_connected_sem, wifi_event_handler, wifi_init_sta)
// SNTP Time Sync Notification Callback
void time_sync_notification_cb(struct timeval *tv) {
ESP_LOGI(TAG, "Time synchronization event received!");
ESP_LOGI(TAG, "Time successfully synchronized: %s", ctime(&tv->tv_sec)); // ctime adds newline
// The system time is typically set automatically by the SNTP service.
// If you needed to set it manually (e.g., from a different time source):
// struct timeval tv_set = { .tv_sec = tv->tv_sec, .tv_usec = tv->tv_usec };
// settimeofday(&tv_set, NULL); // Set system time
// Set the timezone - Example: Eastern Standard Time (EST5EDT)
// For other timezones, find the correct TZ string.
// India Standard Time: "IST-5:30"
// Central European Time (Berlin): "CET-1CEST,M3.5.0,M10.5.0/3"
const char* tz_string = "EST5EDT,M3.2.0/2,M11.1.0/2"; // US Eastern Time
// const char* tz_string = "CET-1CEST,M3.5.0/2,M10.5.0/3"; // Central European Summer Time
setenv("TZ", tz_string, 1);
tzset(); // Apply the TZ environment variable
ESP_LOGI(TAG, "Timezone set to: %s", tz_string);
}
static void display_time_task(void *pvParameters) {
char strftime_buf[64];
time_t now;
struct tm timeinfo;
struct timeval tv_now;
while (1) {
// Wait for 1 second
vTaskDelay(pdMS_TO_TICKS(1000));
// Check if SNTP is synchronized
if (esp_sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
gettimeofday(&tv_now, NULL); // Get current system time
now = tv_now.tv_sec;
// Convert to local time based on TZ setting
localtime_r(&now, &timeinfo);
// Format time
strftime(strftime_buf, sizeof(strftime_buf), "%A, %B %d, %Y %I:%M:%S %p %Z (%z)", &timeinfo);
ESP_LOGI(TAG, "Current local time: %s", strftime_buf);
} else {
ESP_LOGI(TAG, "Time not yet synchronized. Status: %d", esp_sntp_get_sync_status());
}
}
}
static void initialize_sntp(void) {
ESP_LOGI(TAG, "Initializing SNTP");
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_set_time_sync_notification_cb(time_sync_notification_cb);
esp_sntp_init();
ESP_LOGI(TAG, "SNTP initialized. Waiting for time synchronization...");
}
void app_main(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "SNTP Local Time Example Start");
wifi_init_sta(); // Assuming wifi_init_sta and its handler are defined as in Example 1
ESP_LOGI(TAG, "Waiting for Wi-Fi connection...");
if(xSemaphoreTake(wifi_connected_sem, portMAX_DELAY) == pdTRUE) {
ESP_LOGI(TAG, "Wi-Fi Connected. Initializing SNTP.");
initialize_sntp();
// Start the task to display time periodically
xTaskCreate(display_time_task, "display_time_task", 4096, NULL, 5, NULL);
} else {
ESP_LOGE(TAG, "Failed to connect to Wi-Fi.");
}
}
2. Build, Flash, and Observe:
- After Wi-Fi connection and SNTP synchronization, the
display_time_task
will start printing the current local time (according to theTZ
string you set) every second. - You should see the time formatted correctly, including the timezone abbreviation and offset.
Tip: You can find lists of
TZ
strings for various timezones online (e.g., search for “Olson timezone database TZ strings” or “POSIX TZ strings”). Ensure the format is compatible with the C library’stzset
expectations.
Variant Notes
- ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6: All these Wi-Fi capable variants fully support the
esp_sntp
component. The resource usage (RAM, CPU) for SNTP is minimal. - ESP32-H2: This variant primarily features IEEE 802.15.4 (Thread, Zigbee) and Bluetooth LE, and lacks built-in Wi-Fi.
- To use SNTP, the ESP32-H2 would need to be part of an IP network, typically via a border router in a Thread network or a similar gateway for other 802.15.4 setups that provide IP connectivity.
- If the ESP32-H2 has access to an IP layer (e.g., over 6LoWPAN provided by a Thread border router), and can reach an NTP server, the
esp_sntp
component could theoretically be used. However, the network setup is more complex than with Wi-Fi variants. - Alternatively, time could be relayed to the ESP32-H2 from a gateway device that itself is SNTP synchronized.
General Considerations:
- RTC Accuracy: While SNTP synchronizes the system time, the ESP32 also has an internal RTC that can maintain time during sleep modes. The accuracy of this RTC can vary. SNTP helps correct any drift when the device is active and connected.
CONFIG_LWIP_DHCP_MAX_NTP_SERVERS
: If you obtain NTP server information via DHCP (Option 42), this Kconfig option sets how many servers can be processed. By default,esp_sntp
uses manually configured servers.
Common Mistakes & Troubleshooting Tips
Mistake / Issue | Symptom(s) | Troubleshooting / Solution |
---|---|---|
SNTP Not Initializing (No Wi-Fi/IP) | SNTP sync callback never called. esp_sntp_get_sync_status() remains SNTP_SYNC_STATUS_RESET . Logs may show DNS errors for NTP server. |
Mistake: Calling esp_sntp_init() before Wi-Fi is connected and an IP address is obtained.Fix: Ensure Wi-Fi connection and IP acquisition precede SNTP initialization. Use event handlers or semaphores for proper sequencing. |
Incorrect NTP Server Address | Time synchronization fails or is erratic. Callback may not be called, or time is wildly incorrect. |
Mistake: Using an invalid, misspelled, or unreachable NTP server hostname/IP in esp_sntp_setservername() .Fix: Verify server address(es). Use reliable public servers (e.g., “pool.ntp.org”, “time.google.com”) or a known local NTP server. Check DNS resolution if using hostnames. |
Firewall Blocking UDP Port 123 | SNTP requests time out. No response from NTP server. Sync fails. |
Mistake: Network firewall (local, router, ISP) blocking outbound UDP traffic on port 123. Fix: Ensure the network allows outbound UDP on port 123 to the NTP servers. Test from a different network if possible. |
Timezone (TZ) String Incorrect or Not Applied | localtime_r() returns UTC time, or incorrect local time, or DST is not handled correctly. |
Mistake: TZ environment variable not set, set with an invalid format, or tzset() not called after setenv("TZ", ...) .Fix: Use a valid POSIX TZ string. Call setenv("TZ", "YOUR_TZ_STRING", 1); then tzset(); . Do this once, typically after the first successful SNTP sync.
|
Forgetting Time Sync Notification Callback | Application has no reliable way to know when system time is first synchronized and valid. May use uninitialized time. |
Mistake: Not registering a callback using esp_sntp_set_time_sync_notification_cb() .Fix: Always register the callback to get notified when time is synchronized. Perform actions like setting TZ or enabling time-dependent tasks within or after this callback. |
System Time Not Set by SNTP | gettimeofday() or time() return epoch time (or incorrect time) even after SNTP callback. |
Mistake: Assuming SNTP callback *only* notifies and doesn’t set system time. (Note: esp_sntp usually sets system time automatically).Fix: The esp_sntp service typically calls settimeofday() internally. Verify this behavior. If manual setting is needed (rare with esp_sntp ), call settimeofday(tv, NULL) in the sync callback with the provided struct timeval *tv .
|
Large Time Jumps Disrupting Application | Application logic sensitive to monotonic time or short intervals behaves erratically after a large time correction. |
Mistake: Default immediate time adjustment (SNTP_SYNC_MODE_IMMED ) causes issues.Fix: Consider using esp_sntp_set_sync_mode(SNTP_SYNC_MODE_SMOOTH); for gradual clock adjustments (slewing). Be aware smooth adjustment takes longer for large offsets.
|
SNTP Stops After Some Time | Time updates cease after initial synchronization or after a period of operation. |
Mistake: esp_sntp_stop() called inadvertently. Wi-Fi disconnection not handled leading to SNTP failure. Task stack overflow for SNTP task if heavily modified (unlikely with default component).Fix: Ensure esp_sntp_stop() is only called when intended. Implement robust Wi-Fi reconnection logic; SNTP should resume after network is back. Check Kconfig CONFIG_LWIP_SNTP_UPDATE_DELAY for resync interval.
|
Exercises
- Multiple NTP Servers:
- Modify Example 1 to configure and use two different NTP server hostnames (e.g.,
pool.ntp.org
andtime.nist.gov
). Theesp_sntp
component will try them in order or based on its internal logic. Observe if synchronization still occurs.
- Modify Example 1 to configure and use two different NTP server hostnames (e.g.,
- Custom Time Sync Check Task:
- Instead of just relying on the notification callback, create a separate FreeRTOS task that periodically calls
esp_sntp_get_sync_status()
. - This task should log “Waiting for sync…”, “Sync in progress…”, or “Sync completed.” based on the status. Once completed, it can print the current UTC time and then terminate itself or continue monitoring.
- Instead of just relying on the notification callback, create a separate FreeRTOS task that periodically calls
- Timezone Switcher:
- Extend Example 2. Add a mechanism (e.g., a GPIO button press or a command received via UART/WebSocket) that allows switching the
TZ
environment variable between two different timezones (e.g., “PST8PDT” and “CET-1CEST”). - After switching, ensure
tzset()
is called, and observe thedisplay_time_task
printing the time for the newly selected timezone.
- Extend Example 2. Add a mechanism (e.g., a GPIO button press or a command received via UART/WebSocket) that allows switching the
Summary
- Accurate time is crucial for ESP32 applications for logging, scheduling, security, and more.
- SNTP (Simple Network Time Protocol) allows devices to synchronize their clocks with NTP servers over a network.
- ESP-IDF provides the
esp_sntp
component for easy SNTP client implementation. - Key steps include configuring NTP servers (
esp_sntp_setservername
), registering a sync notification callback (esp_sntp_set_time_sync_notification_cb
), and initializing (esp_sntp_init
). - Once synchronized, system time can be set (often automatically by
esp_sntp
) and retrieved using standard C library functions (time()
,gettimeofday()
). - Local time display requires setting the
TZ
environment variable with a POSIX-compliant string defining the timezone and DST rules, followed by callingtzset()
. - Functions like
localtime_r()
andstrftime()
are then used to convert and format time. - SNTP is supported on all Wi-Fi enabled ESP32 variants; ESP32-H2 requires IP connectivity via a gateway.
Further Reading
- ESP-IDF SNTP Documentation: API reference for
esp_sntp.h
in your ESP-IDF docs or on the Espressif website. (https://docs.espressif.com/projects/esp-idf/en/v5.4/esp32/api-reference/protocols/esp_sntp.html
) - RFC 4330 – Simple Network Time Protocol (SNTP) Version 4: The official SNTP specification. (
https://tools.ietf.org/html/rfc4330
) - C Library Time Functions: Man pages or documentation for
time.h
functions (time
,localtime_r
,strftime
,setenv
,tzset
). - POSIX
TZ
string format: Search for documentation on the format of theTZ
environment variable (e.g., GNU C Library manual or other POSIX references).
