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
group bt_mesh_time_srv

API for the Time Server model.

Defines

BT_MESH_TIME_SRV_INIT(_time_update_cb)

Initialization parameters for a Time Server model instance.

Parameters:
  • _time_update_cb[in] Time update callback (optional).

BT_MESH_MODEL_TIME_SRV(_srv)

Time Server model composition data entry.

Parameters:

Enums

enum bt_mesh_time_update_types

Time srv update types

Values:

enumerator BT_MESH_TIME_SRV_STATUS_UPDATE

Status update

enumerator BT_MESH_TIME_SRV_SET_UPDATE

Set update

enumerator BT_MESH_TIME_SRV_ZONE_UPDATE

Zone update

enumerator BT_MESH_TIME_SRV_UTC_UPDATE

UTC update

Functions

int bt_mesh_time_srv_time_status_send(struct bt_mesh_time_srv *srv, struct bt_mesh_msg_ctx *ctx)

Publish an unsolicited Time Status message.

Parameters:
  • srv[in] Time Server instance.

  • ctx[in] Message context, or NULL to publish with the configured parameters.

Return values:
  • -EOPNOTSUPP – The server Time Role state does not support sending of unsolicited Time Status messages.

  • -EADDRNOTAVAIL – A message context was not provided and publishing is not configured.

  • -EAGAIN – The device has not been provisioned.

Returns:

0 Successfully published the current Time state.

int bt_mesh_time_srv_status(struct bt_mesh_time_srv *srv, uint64_t uptime, struct bt_mesh_time_status *status)

Get the time status at a given uptime.

Converts the device uptime (from k_uptime_get) to TAI time.

Note

This function does not support status lookups for uptimes that occurred before the last received set- or status update. In addition, lookups for uptimes that happened before k_uptime_get must be used with caution, since there is no guarantee that the output data is fully reliable.

Parameters:
  • srv[in] Time Server instance.

  • uptime[in] Uptime at the desired point in time.

  • status[out] The device’s Time Status at the given uptime.

Return values:
  • 0 – Successfully retrieved the Time status.

  • -EAGAIN – Invalid uptime input. Asking for status that is in the past.

int64_t bt_mesh_time_srv_mktime(struct bt_mesh_time_srv *srv, struct tm *timeptr)

Get the device uptime at a given local time.

Note

This function does not support lookups for local time that occurred before the TAI-time of the last received set- or status update. In addition, lookups for local time that happened before k_uptime_get must be used with caution, since there is no guarantee that the output data is fully reliable.

Note

Whenever a zone or UTC change makes the local time jump forward in time, i.e. for day light savings adjustments, we get a situation where some points in time will occur twice on the local timeline. If the user where to input a local time that is affected by these changes the response uptime will reflect the input local time added with the new adjustment. Example: A time zone change is scheduled 12:00:00 a clock an arbitrary day to set the local clock forward by one hour. If the user requests the uptime for 12:00:05 at that same day, the returned uptime will be the same as if the user inputted the local time 13:00:05 of that day.

Note

Whenever a zone or UTC change makes the local time jump backwards in time, i.e. for day light savings adjustments, we get a situation where some points in time never will on the local timeline. If the user where to input a local time that is affected by these changes the response uptime will reflect the local time instance that happens last. Example: A time zone change is scheduled 12:00:00 a clock an arbitrary day to set the local clock back one hour. If the user requests the uptime for 12:00:00 at that same day, the returned uptime will reflect the local time that under normal circumstances would be 13:00:00 that day.

Parameters:
  • srv[in] Time Server instance.

  • timeptr[in] Local time at the desired point.

Return values:

Positive – uptime if successful, otherwise negative error value.

struct tm *bt_mesh_time_srv_localtime_r(struct bt_mesh_time_srv *srv, int64_t uptime, struct tm *timeptr)

Get the device local time at a given uptime.

Note

This function does not support local time lookups for uptimes that occurred before the last received set- or status update. In addition, lookups for uptimes that happened before k_uptime_get must be used with caution, since there is no guarantee that the output data is fully reliable.

Parameters:
  • srv[in] Time Server instance.

  • uptime[in] Uptime at the desired point in time.

  • timeptr[out] The device’s local time at the given uptime.

Return values:

Pointer – to time struct if successful, NULL otherwise.

struct tm *bt_mesh_time_srv_localtime(struct bt_mesh_time_srv *srv, int64_t uptime)

Get the device local time at a given uptime.

Note

This function does not support local time lookups for uptimes that occurred before the last received set- or status update. In addition, lookups for uptimes that happened before k_uptime_get must be used with caution, since there is no guarantee that the output data is fully reliable.

Parameters:
  • srv[in] Time Server instance.

  • uptime[in] Uptime at the desired point in time.

Return values:

Pointer – to time struct if successful, NULL otherwise.

uint64_t bt_mesh_time_srv_uncertainty_get(struct bt_mesh_time_srv *srv, int64_t uptime)

Get the device time uncertainty at a given uptime.

Note

This function does not support uncertainty lookups for uptimes that occurred before the last received set- or status update.

Parameters:
  • srv[in] Time Server instance.

  • uptime[in] Uptime at the desired point in time.

Return values:

Positive – uncertainty if successful, negative error code otherwise.

void bt_mesh_time_srv_time_set(struct bt_mesh_time_srv *srv, int64_t uptime, const struct bt_mesh_time_status *status)

Set the TAI status parameters for the server.

Note

This is an alternative way to set parameters for the Time Server instance. In most cases these parameters will be configured remotely by a Time Client or a neigbouring Time Server.

Parameters:
  • srv[in] Time Server instance.

  • uptime[in] Desired uptime to set.

  • status[in] Desired status configuration.

void bt_mesh_time_srv_time_zone_change_set(struct bt_mesh_time_srv *srv, const struct bt_mesh_time_zone_change *data)

Set the zone change parameters for the server.

Note

This is an alternative way to set parameters for the Time Server instance. In most cases these parameters will be configured remotely by a Time Client or a neigbouring Time Server.

Parameters:
  • srv[in] Time Server instance.

  • data[in] Desired zone change configuration.

void bt_mesh_time_srv_tai_utc_change_set(struct bt_mesh_time_srv *srv, const struct bt_mesh_time_tai_utc_change *data)

Set the TAI UTC change parameters for the server.

Note

This is an alternative way to set parameters for the Time Server instance. In most cases these parameters will be configured remotely by a Time Client or a neigbouring Time Server.

Parameters:
  • srv[in] Time Server instance.

  • data[in] Desired TAI UTC change configuration.

void bt_mesh_time_srv_role_set(struct bt_mesh_time_srv *srv, enum bt_mesh_time_role role)

Set the role parameter for the server.

Note

This is an alternative way to set parameters for the Time Server instance. In most cases these parameters will be configured remotely by a Time Client or a neigbouring Time Server.

Parameters:
  • srv[in] Time Server instance.

  • role[in] Desired role configuration.

struct bt_mesh_time_srv_data
#include <time_srv.h>

Internal data structure of the Time Server instance.

struct bt_mesh_time_srv
#include <time_srv.h>

Time Server instance.

Should be initialized with BT_MESH_TIME_SRV_INIT.

Public Members

const struct bt_mesh_model *model

Model entry.

struct bt_mesh_model_pub pub

Publish parameters.

struct bt_mesh_model_pub setup_pub

Setup model publish parameters

struct bt_mesh_msg_ack_ctx ack_ctx

Acknowledged message tracking.

struct bt_mesh_time_srv_data data

Model state structure

struct k_work_delayable status_delay

Delayable work to randomize status relaying.

bool is_unsolicited

Property whether the Time status was unsolicited.

uint8_t cached_ttl

Cached publishing TTL while server is sending the unsolicited Time Status with zero TTL.

void (*const time_update_cb)(struct bt_mesh_time_srv *srv, struct bt_mesh_msg_ctx *ctx, enum bt_mesh_time_update_types type)

Update callback.

Called whenever the server receives a state update from either:

  • bt_mesh_time_cli_time_set

  • bt_mesh_time_cli_zone_set

  • bt_mesh_time_cli_tai_utc_delta_set

  • bt_mesh_time_srv_time_status_send

Note

This handler is optional.

Param srv:

[in] Server instance.