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 oflocaltime
.
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:
CONFIG_BT_MESH_TIME_MESH_HOP_UNCERTAINTY
: The amount of uncertainty introduced in the mesh stack through sending a single message, in milliseconds.CONFIG_BT_MESH_TIME_SRV_CLOCK_ACCURACY
: The largest possible clock drift introduced by the kernel clock’s hardware, in parts per million.
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-bitsubsec
. 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
include/bluetooth/mesh/time_srv.h
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:
_srv – [in] Pointer to a Time Server model instance.
Enums
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.
-
const struct bt_mesh_model *model
-
BT_MESH_TIME_SRV_INIT(_time_update_cb)