4.8. NTP-based time synchronization
4.8.1. Overview
This example demonstrates how to use the Anjay Lite NTP module to synchronize the device clock with an NTP server. It covers:
enabling NTP support in the build configuration,
configuring and initializing the NTP module,
implementing an NTP event callback to handle synchronization events,
synchronizing the application time.
Note
Code related to this tutorial can be found under examples/tutorial/AT-TimeSynchronization in the Anjay Lite source directory.
4.8.2. Build configuration
The example enables NTP support explicitly and disables the default POSIX time compatibility layer:
set(ANJ_WITH_NTP ON)
set(ANJ_WITH_TIME_POSIX_COMPAT OFF)
Note
With ANJ_WITH_TIME_POSIX_COMPAT disabled, the application must provide its
own implementations of anj_time_monotonic_now and anj_time_real_now.
4.8.3. NTP module initialization
The NTP module is initialized by preparing an anj_ntp_configuration_t
structure and passing it to anj_ntp_init. The configuration includes
the NTP server addresses, synchronization period, response timeout, and the
event callback function. The overall logic is based on the implementation of
the Time Synchronization Object with ID 3415.
This Object defines resources for configuring NTP parameters (server URIs,
synchronization period) and monitoring synchronization status.
As part of anj_ntp_init, an instance of this Object is also
installed in the Anjay Lite’s Data Model.
anj_ntp_t ntp;
anj_ntp_configuration_t ntp_config = {
.event_cb = ntp_event_callback,
.ntp_server_address = "pool.ntp.org",
.backup_ntp_server_address = "time.google.com",
.ntp_period_hours = 1,
.response_timeout = anj_time_duration_new(3, ANJ_TIME_UNIT_S),
};
if (anj_ntp_init(&anj, &ntp, &ntp_config)) {
log(L_ERROR, "Failed to initialize NTP module");
return -1;
}
4.8.4. NTP event callback
The NTP event callback function handles various NTP events, such as the start
of synchronization, successful completion, and errors. It can be used to log
the current status and update the application time accordingly. In the provided
example, anj_ntp_start is called to initiate synchronization in two cases:
when the NTP module is initialized and when the synchronization period has
elapsed.
In the case of successful synchronization, the callback updates the
g_last_sync_real and g_last_sync_monotonic variables to reflect the
new synchronized time and the corresponding monotonic time. It also sets the
g_time_synced flag to indicate that the time has been successfully
synchronized. For embedded applications, this can also be the place where the
RTC module or system clock is adjusted to the newly synchronized time.
static void ntp_event_callback(void *arg,
anj_ntp_t *ntp,
anj_ntp_status_t status,
anj_time_real_t synchronized_time) {
(void) arg;
switch (status) {
case ANJ_NTP_STATUS_INITIAL:
case ANJ_NTP_STATUS_PERIOD_EXCEEDED: {
log(L_INFO, "NTP synchronization started");
anj_ntp_start(ntp);
break;
}
case ANJ_NTP_STATUS_FINISHED_WITH_ERROR: {
log(L_ERROR, "NTP synchronization failed");
break;
}
case ANJ_NTP_STATUS_FINISHED_SUCCESSFULLY: {
log(L_INFO, "NTP synchronization succeeded");
log(L_INFO, "\n Old time: %s\n New time: %s\n Delta: %f s",
ANJ_TIME_REAL_AS_STRING(g_last_sync_real, ANJ_TIME_UNIT_MS),
ANJ_TIME_REAL_AS_STRING(synchronized_time, ANJ_TIME_UNIT_MS),
((double) anj_time_duration_to_scalar(
anj_time_real_diff(synchronized_time, anj_time_real_now()),
ANJ_TIME_UNIT_MS))
/ 1000.0);
g_time_synced = true;
g_last_sync_real = synchronized_time;
g_last_sync_monotonic = anj_time_monotonic_now();
break;
}
default:
break;
}
}
4.8.5. Custom time functions
With the default POSIX time compatibility layer disabled, the application
must provide its own implementations of anj_time_monotonic_now and
anj_time_real_now. These functions should return the current monotonic and
real time, respectively. In this example, clock_gettime is used to retrieve
the current monotonic time from the system clock.
The anj_time_real_now function calculates the current real time based on
the last synchronized real time and the elapsed monotonic duration since the
last synchronization.
static anj_time_real_t g_last_sync_real;
static anj_time_monotonic_t g_last_sync_monotonic;
static bool g_time_synced = false;
static int64_t get_time(clockid_t clk_id) {
struct timespec res;
if (clock_gettime(clk_id, &res)) {
return 0;
}
return (int64_t) res.tv_sec * 1000 * 1000 + (int64_t) res.tv_nsec / 1000;
}
anj_time_monotonic_t anj_time_monotonic_now(void) {
#ifdef CLOCK_MONOTONIC
return anj_time_monotonic_new(get_time(CLOCK_MONOTONIC), ANJ_TIME_UNIT_US);
#else /* CLOCK_MONOTONIC */
log(L_ERROR, "CLOCK_MONOTONIC is not available on this platform");
return anj_time_monotonic_new(0, ANJ_TIME_UNIT_US);
#endif /* CLOCK_MONOTONIC */
}
anj_time_real_t anj_time_real_now(void) {
anj_time_duration_t delta = anj_time_duration_sub(
anj_time_monotonic_to_duration(anj_time_monotonic_now()),
anj_time_monotonic_to_duration(g_last_sync_monotonic));
return anj_time_real_add(g_last_sync_real, delta);
}
4.8.6. Delaying normal operation until time is synchronized
In many deployments, it is desirable to start the main LwM2M loop only after the device has a valid notion of real time (for example, to correctly handle credential expiration or time-based application logic).
This example demonstrates a simple approach where the main loop first
periodically calls anj_ntp_step until the time is synchronized. Once
synchronization is confirmed (by checking the g_time_synced flag), normal
Anjay operation can proceed.
After the first synchronization, the NTP module will continue to
synchronize the time periodically based on the configured synchronization
period. This is why anj_ntp_step still needs to be called in each iteration
of the main loop.
while (true) {
usleep(50 * 1000);
anj_ntp_step(&ntp);
if (g_time_synced) {
// Once time is synchronized, we can proceed to normal Anjay
// operation
anj_core_step(&anj);
}
}
4.8.7. Summary
This example illustrated how to integrate the Anjay Lite NTP module into an application for time synchronization. By following the steps outlined above, developers can ensure their devices maintain accurate time, which is crucial for various embedded applications.