Time Server

The Time Server model allows mesh nodes to synchronize the current time and date by publishing and subscribing to Time Status messages.

The Time Server model relies on an external clock source to provide the current time and date, but takes care of distributing this information through the network.

The Time Server model adds the following new model instances in the composition data:

  • Time Server

  • Time Setup Server

Operation

The Time Server model builds an ad-hoc time synchronization hierarchy through the mesh network that propagates the current wall-clock time as TAI timestamps.

To ensure consistent synchronization, the Time Server model should be configured to publish its Time Status with regular intervals using a Configuration Client model.

Note

To ensure a high level of accuracy, Time Status messages are always transmitted one hop at a time if it is sent as an unsolicited message. Every mesh node without access to the Time Authority that needs access to the common time reference must be within the radio range of at least one other node with the Time Server model. Time Server nodes that have no neighboring Time Servers need to get this information in some other way (for example, getting time from the Time Authority).

Roles

The Time Server model operates in 3 different roles:

Time Authority

A Time Server that has access to an external clock source, and can propagate the current time to other nodes in the network. The Time Authority will publish Time Status messages, but will not receive them.

Time Relay

A Time Server that both publishes and receives Time Status messages. The Time Relay relies on other mesh nodes to tell time.

Time Client

A Time Server that receives Time Status messages, but does not relay them. The Time Client relies on other mesh nodes to tell time.

Whenever a Time Server model that is not a Time Authority receives a Time Status message, it will update its internal clock to match the new Time Status - if it improves its clock uncertainty.

The Time Authority role should not be confused with the Authority state, which denotes whether the device is actively monitoring a reliable external time source.

Clock uncertainty

The Time Server’s time synchronization mechanism is not perfect, and every timestamp it provides has some uncertainty. The clock uncertainty is measured in milliseconds, and can come from various sources, like inaccuracies in the original clock source, internal clock drift or uncertainty in the mesh stack itself. The Time Server inherits the uncertainty of the node it got its Time Status from, and the clock uncertainty of each mesh node increases for every hop the Time Status had to make from the Time Authority.

The expected uncertainty in the mesh stack comes from the fluctuation in time to encrypt a message and broadcast it, and can be configured through the CONFIG_BT_MESH_TIME_MESH_HOP_UNCERTAINTY.

In addition to the uncertainty added by the mesh stack itself, the internal clock of each device will increase the uncertainty of the timestamp over time. As crystal hardware is not perfectly accurate, it will gradually drift away from the correct time. The potential range of this clock drift is generally a known property of the onboard hardware, even if the actual drift fluctuates. As it is impossible to measure the actual clock drift at any given moment, the Time Server gradually increases the uncertainty of its timestamp based on the known clock accuracy.

The Time Server’s notion of the local clock accuracy can be configured with CONFIG_BT_MESH_TIME_SRV_CLOCK_ACCURACY. By default, it uses the configured kernel clock control accuracy.

Leap seconds and time zones

As described in the section on International atomic time (TAI), the Time Server measures time as TAI seconds, and adds the UTC leap second offset and local time zone as additional information.

Both leap seconds and time zone changes are irregular events that are scheduled by governing boards ahead of time. Because of this, it is impossible to algorithmically determine the time zone and UTC offset for future dates, as there is no way to tell when and how many changes will be scheduled.

Note

The time zone of the device not only changes with its location on the globe, but also with Daylight Saving Time. For instance, Central Europe is in the UTC+2 (CEST) time zone during the summer, but the UTC+1 (CET) time zone during the winter.

Time Servers’ Time Status messages include the current time zone and UTC offset, but Time models may also distribute information about known future changes to these states. For instance, if a Time Authority node learns through its time source that the device will change to Daylight Saving Time on March 29th, it can broadcast a Time Zone change message, which includes the new time zone offset as well as the TAI timestamp of the change. All Time Server models that receive this message will automatically store this change and notify the application. The application can then reschedule any timeouts that happen after the change to reflect the new offset.

Timestamp conversion

To convert between human-readable time and device time, the Time Server model API includes three functions with signatures similar to the C standard library’s time.h API:

  • bt_mesh_time_srv_mktime(): Get the uptime at a specific date/time.

  • bt_mesh_time_srv_localtime(): Get the local date/time at a specific uptime.

  • bt_mesh_time_srv_localtime_r(): A thread safe version of localtime.

For example, if you want to schedule your mesh device to send up fireworks exactly at midnight on New Year’s Eve, you can use mktime to find the device uptime at this exact timestamp:

void schedule_fireworks(void)
{
   struct tm new_years_eve = {
      .tm_year = 2021 - 1900, /* struct tm measures years since 1900 */
      /* January 1st: */
      .tm_mon = 0,
      .tm_mday = 1,
      /* Midnight: */
      .tm_hour = 0,
      .tm_min = 0,
      .tm_sec = 0,
   };

   int64_t uptime = bt_mesh_time_srv_mktime(&time_srv, &new_years_eve);
   if (uptime < 0) {
      /* Time Server does not know */
      return;
   }

   k_timer_start(&start_fireworks, uptime - k_uptime_get(), 0);
}

And, to print the current date and time, you can use localtime:

void print_datetime(void)
{
   struct tm *today = bt_mesh_time_srv_localtime(&time_srv, k_uptime_get());
   if (!today) {
      /* Time Server does not know */
      return;
   }

   const char *weekdays[] = {
      "Sunday",
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday",
   };

   printk("Today is %s %04u-%02u-%02u\n", weekdays[today->tm_wday],
         today->tm_year + 1900, today->tm_mon + 1, today->tm_mday);
   printk("The time is %02u:%02u\n", today->tm_hour, today->tm_min);
}

Additionally, the Time Server API includes bt_mesh_time_srv_uncertainty_get(), which allows the application to determine the current uncertainty of a specific uptime. This function can be used in combination with the three others to determine the accuracy of a provided timestamp.

Note

All time and uncertainty conversion is based on the Time Server’s current data, and assumes that no corrections are made between the call and the provided timestamp. Timestamps that are weeks or months into the future may have an uncertainty of several hours, due to clock drift. The application can subscribe to changes in the Time Server state through the bt_mesh_time_srv_cb callback structure.

Any time zone or UTC delta changes are taken into account.

Configuration

The clock uncertainty of the Time Server model can be configured with the following configuration options:

States

The Time Server model contains the following states:

TAI time: bt_mesh_time_tai

The TAI time is a composite state, with members sec and an 8-bit subsec. If the current time is known, the TAI time changes continuously.

Uncertainty: uint64_t

Current clock uncertainty in milliseconds. Without new data, clock uncertainty increases gradually due to clock drift.

UTC delta: int16_t

Number of seconds between the TAI and UTC timestamps due to UTC leap seconds.

Time zone offset: int16_t

Local time zone offset in 15-minute increments.

Authority: bool

Whether this device has continuous access to a reliable TAI source, such as a GPS receiver or an NTP-synchronized clock. The Authority state does not transfer to other devices.

Role: bt_mesh_time_role

The Time Server’s current role in the Time Status propagation.

Time zone change: bt_mesh_time_zone_change

The Time zone change state determines the next scheduled change in time zones, and includes both the new time zone offset and the timestamp of the scheduled change. If no change is known, the timestamp is 0.

UTC delta change: bt_mesh_tai_utc_change

The UTC delta change state determines the next scheduled leap second, and includes both the new UTC offset and the timestamp of the scheduled change. If no change is known, the timestamp is 0.

Extended models

None.

Persistent storage

The Timer Server stores the following states persistently:

  • Role

  • Time zone change

  • UTC delta change

All other states change with time, and are not stored.

API documentation

Header file: include/bluetooth/mesh/time_srv.h
Source file: subsys/bluetooth/mesh/time_srv.c
Time Server model