Skip to content

Commit 4bf5116

Browse files
committed
bluetooth: services: add Elapsed Time Service support
Added support for elapsed time service (ETS). The service is introduced currently with the experimental tag. Signed-off-by: Dipak Shetty <[email protected]>
1 parent aefc8c5 commit 4bf5116

File tree

5 files changed

+1033
-0
lines changed

5 files changed

+1033
-0
lines changed
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
/*
2+
* Copyright (c) 2025 Dipak Shetty
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_ETS_H_
8+
#define ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_ETS_H_
9+
10+
/**
11+
* @brief Elapsed Time Service (ETS)
12+
* @defgroup bt_ets Elapsed Time Service (ETS)
13+
* @ingroup bluetooth
14+
* @{
15+
*
16+
*/
17+
18+
#include <stdint.h>
19+
#include <stdbool.h>
20+
#include <zephyr/sys/util.h>
21+
22+
#ifdef __cplusplus
23+
extern "C" {
24+
#endif
25+
26+
/**
27+
* @brief Elapsed Time data structure as defined in GATT Specification Supplement Section 3.82.
28+
*/
29+
struct bt_ets_elapsed_time {
30+
/** Flags field. @see @ref BT_ETS_FLAGS. */
31+
uint8_t flags;
32+
/** The actual time value in the resolution as defined by the flags.
33+
* The Time Value field contains a counter of the number of time units as determined by the
34+
* time resolution of the clock. The starting point of the timeline is 2000-01-01 00:00:00
35+
* when reporting a time of day or is implementation-dependent for a tick counter.
36+
*/
37+
uint8_t time_value[6];
38+
/** Time synchronization source type. @see @ref BT_ETS_TIME_SOURCE */
39+
uint8_t time_sync_src;
40+
/**Combined Time Zone Daylight Saving Time offset from UTC in 15-minute units (signed). */
41+
int8_t tz_dst_offset;
42+
} __packed;
43+
44+
/**
45+
* @brief Elapsed Time Service flags field bits (GATT Specification Supplement Section 3.82)
46+
* @name Elapsed Time Service Flags
47+
* @anchor BT_ETS_FLAGS
48+
*
49+
* @{
50+
*/
51+
52+
/** Time Value reports a counter (tick counter). If 0, Time Value reports a time of day. */
53+
#define BT_ETS_FLAG_TICK_COUNTER BIT(0)
54+
55+
/** Time is UTC. If 0, time is local time. Has no meaning for tick counter. */
56+
#define BT_ETS_FLAG_UTC BIT(1)
57+
58+
/** Time resolution mask (bits 2-3). */
59+
#define BT_ETS_FLAG_RESOLUTION_MASK (BIT(2) | BIT(3))
60+
61+
/** TZ/DST offset is used. If 0, offset not used. Has no meaning for tick counter. */
62+
#define BT_ETS_FLAG_TZ_DST_USED BIT(4)
63+
64+
/** Time stamp is from current timeline. */
65+
#define BT_ETS_FLAG_CURRENT_TIMELINE BIT(5)
66+
67+
/** Reserved bits (6-7). */
68+
#define BT_ETS_FLAG_RESERVED_MASK (BIT(6) | BIT(7))
69+
70+
/** @} */
71+
72+
/**
73+
* @brief Time resolution values for bits 2-3 of flags field.
74+
* @defgroup bt_ets_time_resolution Time Resolution
75+
* @{
76+
*/
77+
enum bt_ets_time_resolution {
78+
/** 1 second resolution. */
79+
BT_ETS_RESOLUTION_1_SEC = 0,
80+
/** 100 milliseconds resolution. */
81+
BT_ETS_RESOLUTION_100_MS = 1,
82+
/** 1 millisecond resolution. */
83+
BT_ETS_RESOLUTION_1_MS = 2,
84+
/** 100 microseconds resolution. */
85+
BT_ETS_RESOLUTION_100_US = 3,
86+
};
87+
88+
/** @} */
89+
90+
/**
91+
* @brief Time synchronization source values (GATT Specification Supplement Section 3.243)
92+
* @name Time Source Values
93+
* @anchor BT_ETS_TIME_SOURCE
94+
*
95+
* @{
96+
*/
97+
98+
/** Unknown time source. */
99+
#define BT_ETS_TIME_SOURCE_UNKNOWN 0
100+
/** Network Time Protocol. */
101+
#define BT_ETS_TIME_SOURCE_NTP 1
102+
/** GPS/GNSS. */
103+
#define BT_ETS_TIME_SOURCE_GPS 2
104+
/** Radio Time Signal. */
105+
#define BT_ETS_TIME_SOURCE_RADIO 3
106+
/** Manually set. */
107+
#define BT_ETS_TIME_SOURCE_MANUAL 4
108+
/** Atomic Clock. */
109+
#define BT_ETS_TIME_SOURCE_ATOMIC 5
110+
/** Cellular Network. */
111+
#define BT_ETS_TIME_SOURCE_CELLULAR 6
112+
/** Not synchronized. */
113+
#define BT_ETS_TIME_SOURCE_NOT_SYNCHRONIZED 7
114+
115+
/** @} */
116+
117+
/**
118+
* @brief Write result codes for write_elapsed_time callback.
119+
* @defgroup bt_ets_write_result Write Result Codes
120+
*
121+
* These codes indicate the result of a time write operation and map to
122+
* specific ATT error responses as defined in the ETS specification.
123+
*
124+
* @{
125+
*/
126+
enum bt_ets_write_result {
127+
/** Write accepted successfully. */
128+
BT_ETS_WRITE_SUCCESS = 0,
129+
/** Time source quality is too low. Rejects write with ATT error 0x80. */
130+
BT_ETS_WRITE_TIME_SOURCE_TOO_LOW = 1,
131+
/** Time value is out of acceptable range. Rejects write with ATT error 0xFF. */
132+
BT_ETS_WRITE_OUT_OF_RANGE = 2,
133+
/** Incorrect time format (flags mismatch). Rejects write with ATT error 0x81. */
134+
BT_ETS_WRITE_INCORRECT_FORMAT = 3,
135+
};
136+
137+
/** @} */
138+
139+
/** Combined supported flags mask based on configuration. */
140+
#define BT_ETS_SUPPORTED_FLAGS_MASK \
141+
((IS_ENABLED(CONFIG_BT_ETS_SUPPORT_TICK_COUNTER) ? BT_ETS_FLAG_TICK_COUNTER : 0) | \
142+
(IS_ENABLED(CONFIG_BT_ETS_SUPPORT_UTC) ? BT_ETS_FLAG_UTC : 0) | \
143+
(IS_ENABLED(CONFIG_BT_ETS_SUPPORT_TZ_DST) ? BT_ETS_FLAG_TZ_DST_USED : 0) | \
144+
(IS_ENABLED(CONFIG_BT_ETS_RESOLUTION_1_SEC) ? 0 \
145+
: IS_ENABLED(CONFIG_BT_ETS_RESOLUTION_100_MS) ? BIT(2) \
146+
: IS_ENABLED(CONFIG_BT_ETS_RESOLUTION_1_MS) ? BIT(3) \
147+
: IS_ENABLED(CONFIG_BT_ETS_RESOLUTION_100_US) ? (BIT(2) | BIT(3)) \
148+
: 0) | \
149+
BT_ETS_FLAG_CURRENT_TIMELINE)
150+
151+
/**
152+
* @brief Clock Status flags (Elapsed Time Service - Section 3.1.1.2)
153+
* @name Clock Status Flags
154+
* @anchor BT_ETS_CLOCK_STATUS
155+
*
156+
* @{
157+
*/
158+
159+
/** Clock needs to be set. If 0, clock is set. */
160+
#define BT_ETS_CLOCK_STATUS_NEEDS_SET BIT(0)
161+
/** Clock Status RFU mask (bits 1-7). */
162+
#define BT_ETS_CLOCK_STATUS_RFU_MASK 0xFE
163+
164+
/** @} */
165+
166+
/**
167+
* @name Clock Capabilities flags (Elapsed Time Service - Section 3.1.1.3)
168+
* @anchor BT_ETS_CLOCK_CAPABILITIES
169+
*
170+
* @{
171+
*/
172+
173+
/** Clock autonomously applies DST rules. */
174+
#define BT_ETS_CLOCK_CAP_DST_AUTO BIT(0)
175+
/** Clock autonomously manages TZ changes. */
176+
#define BT_ETS_CLOCK_CAP_TZ_AUTO BIT(1)
177+
/** Clock Capabilities RFU mask (bits 2-7). */
178+
#define BT_ETS_CLOCK_CAP_RFU_MASK 0xFC
179+
180+
/** @} */
181+
182+
/**
183+
* @brief Elapsed Time Service callback structure.
184+
*/
185+
struct bt_ets_cb {
186+
/**
187+
* @brief Read elapsed time callback (MANDATORY).
188+
*
189+
* Called when a BLE client reads the Current Elapsed Time characteristic.
190+
* Application must fill the elapsed time structure with current time information.
191+
*
192+
* @param time Elapsed time structure to fill.
193+
*
194+
* @retval 0 On success.
195+
* @retval -errno Negative errno on failure.
196+
*/
197+
int (*read_elapsed_time)(struct bt_ets_elapsed_time *time);
198+
199+
/**
200+
* @brief Read clock status (@see @ref BT_ETS_CLOCK_STATUS) callback (MANDATORY).
201+
*
202+
* Called when a BLE client reads the Current Elapsed Time characteristic.
203+
* Application must return the current clock status flags.
204+
*
205+
* @param status Pointer to store clock status byte.
206+
*
207+
* @retval 0 On success.
208+
* @retval -errno Negative errno on failure.
209+
*/
210+
int (*read_clock_status)(uint8_t *status);
211+
212+
/**
213+
* @brief Read clock capabilities (@see @ref BT_ETS_CLOCK_CAPABILITIES) callback
214+
* (MANDATORY).
215+
*
216+
* Called when a BLE client reads the Current Elapsed Time characteristic.
217+
* Application must return the clock capabilities flags.
218+
*
219+
* @param capabilities Pointer to store clock capabilities byte.
220+
*
221+
* @retval 0 On success.
222+
* @retval -errno Negative errno on failure.
223+
*/
224+
int (*read_clock_capabilities)(uint8_t *capabilities);
225+
226+
/**
227+
* @brief Write elapsed time callback (OPTIONAL).
228+
*
229+
* Called when a BLE client writes to the Current Elapsed Time characteristic.
230+
* Only applicable if @kconfig{CONFIG_BT_ETS_CURRENT_ELAPSED_TIME_WRITABLE} is enabled.
231+
* Application should validate time source quality, range, and other constraints.
232+
*
233+
* @note Clock Status and Clock Capabilities are excluded from writes per spec.
234+
*
235+
* @param time Elapsed time structure written by client.
236+
*
237+
* @return Result code indicating acceptance or specific rejection reason.
238+
*/
239+
enum bt_ets_write_result (*write_elapsed_time)(const struct bt_ets_elapsed_time *time);
240+
241+
/**
242+
* @brief Indication subscription changed callback (OPTIONAL).
243+
*
244+
* Called when a client enables or disables indications for the
245+
* Current Elapsed Time characteristic.
246+
*
247+
* @param enabled True if indications enabled, false if disabled.
248+
*/
249+
void (*indication_changed)(bool enabled);
250+
};
251+
252+
/**
253+
* @brief Initialize Elapsed Time Service.
254+
*
255+
* Must be called during initialization. This can be called before or after bt_enable, but before
256+
* advertising.
257+
*
258+
* @param cb Callback structure with mandatory callbacks set.
259+
*
260+
* @retval 0 On success.
261+
* @retval -EINVAL If @p cb is NULL or mandatory callbacks are NULL.
262+
*/
263+
int bt_ets_init(const struct bt_ets_cb *cb);
264+
265+
/**
266+
* @brief Send indication to subscribed clients.
267+
*
268+
* Send indication to all subscribed clients with updated elapsed time.
269+
* This should be called when the time changes other than by natural progression
270+
* (e.g., manual adjustment, external time sync, DST change, timezone change).
271+
*
272+
* @param elapsed_time Elapsed time to indicate.
273+
*
274+
* @retval 0 On success.
275+
* @retval -ENOTCONN If no clients are connected or subscribed.
276+
* @retval -errno Other negative errno on failure.
277+
*/
278+
int bt_ets_indicate(const struct bt_ets_elapsed_time *elapsed_time);
279+
280+
/**
281+
* @brief Decode ETS formatted time into milliseconds since Unix epoch.
282+
*
283+
* Converts ETS time format to milliseconds with Unix epoch (1970-01-01 00:00:00 UTC).
284+
* Handles epoch conversion (ETS uses 2000-01-01 vs Unix 1970-01-01), resolution scaling,
285+
* and timezone conversion based on the flags field in @p et_time.
286+
*
287+
* The output @p unix_ms is always in UTC. Timezone handling based on flags:
288+
* - @kconfig{CONFIG_BT_ETS_SUPPORT_UTC}: Time is already UTC, no conversion needed.
289+
* If @see @ref BT_ETS_FLAG_TZ_DST_USED is set, the offset is informational only.
290+
* - @kconfig{CONFIG_BT_ETS_SUPPORT_LOCAL_TIME} with @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST}:
291+
* If @see @ref BT_ETS_FLAG_TZ_DST_USED is set in @p et_time, converts local time to UTC
292+
* by subtracting the offset: UTC = Local time - TZ/DST Offset × 15 minutes
293+
*
294+
* @note This function is only available when @kconfig{CONFIG_BT_ETS_HELPER_API} is enabled.
295+
* This requires either @kconfig{CONFIG_BT_ETS_SUPPORT_UTC} or
296+
* @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST} to be enabled (proper UTC conversion needs
297+
* either UTC mode or timezone offset information).
298+
*
299+
* @param et_time ETS time formatted structure to decode.
300+
* @param unix_ms Pointer to store milliseconds since Unix epoch (1970-01-01 00:00:00 UTC).
301+
*
302+
* @retval 0 On success.
303+
* @retval -EINVAL If @p et_time or @p unix_ms is NULL.
304+
* @retval -EOVERFLOW If the converted time value overflows during conversion.
305+
*/
306+
int bt_ets_time_to_unix_ms(const struct bt_ets_elapsed_time *et_time, int64_t *unix_ms);
307+
308+
/**
309+
* @brief Encode Unix milliseconds into ETS formatted time.
310+
*
311+
* Converts a Unix timestamp (1970-01-01 00:00:00 UTC) to ETS time format.
312+
* Handles epoch conversion (Unix 1970-01-01 to ETS 2000-01-01), resolution scaling,
313+
* and timezone conversion based on compile-time configuration.
314+
*
315+
* The @p unix_ms input is always in UTC. The @p tz_dst_offset parameter behavior:
316+
* - @kconfig{CONFIG_BT_ETS_SUPPORT_UTC}: Time stays as UTC, no conversion.
317+
* If @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST} enabled, offset is stored for client use.
318+
* - @kconfig{CONFIG_BT_ETS_SUPPORT_LOCAL_TIME} with @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST}:
319+
* Offset is applied to convert UTC to local time and stored in output.
320+
* Formula: Local time = UTC time + TZ/DST Offset × 15 minutes
321+
* @see @ref BT_ETS_FLAG_TZ_DST_USED flag is set in output.
322+
*
323+
* The output @p et_time flags are set based on compile-time configuration:
324+
* - Time mode: @kconfig{CONFIG_BT_ETS_SUPPORT_UTC} or @kconfig{CONFIG_BT_ETS_SUPPORT_LOCAL_TIME}
325+
* - Resolution: CONFIG_BT_ETS_RESOLUTION_* (1_SEC, 100_MS, 1_MS, or 100_US)
326+
* - @see @ref BT_ETS_FLAG_CURRENT_TIMELINE is always set
327+
*
328+
* @note This function is only available when @kconfig{CONFIG_BT_ETS_HELPER_API} is enabled.
329+
* This requires either @kconfig{CONFIG_BT_ETS_SUPPORT_UTC} or
330+
* @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST} to be enabled.
331+
*
332+
* @param et_time Pointer to ETS time structure to fill.
333+
* @param unix_ms Milliseconds since Unix epoch (1970-01-01 00:00:00 UTC) - always UTC.
334+
* @param time_src Time synchronization source type. @see @ref BT_ETS_TIME_SOURCE
335+
* @param tz_dst_offset TZ/DST offset from UTC in 15-minute units (signed).
336+
* Used for UTC-to-local conversion if in LOCAL_TIME mode.
337+
*
338+
* @retval 0 On success.
339+
* @retval -EINVAL If @p et_time is NULL or time is before ETS epoch (2000-01-01) after conversion.
340+
* @retval -EOVERFLOW If the time value exceeds 48-bit range after conversion.
341+
*/
342+
int bt_ets_time_from_unix_ms(struct bt_ets_elapsed_time *et_time, int64_t unix_ms, uint8_t time_src,
343+
int8_t tz_dst_offset);
344+
345+
#ifdef __cplusplus
346+
}
347+
#endif
348+
349+
/**
350+
* @}
351+
*/
352+
353+
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_ETS_H_ */

subsys/bluetooth/services/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ zephyr_sources_ifdef(CONFIG_BT_DIS dis.c)
55

66
zephyr_sources_ifdef(CONFIG_BT_CTS cts.c)
77

8+
zephyr_sources_ifdef(CONFIG_BT_ETS ets.c)
9+
810
zephyr_sources_ifdef(CONFIG_BT_HRS hrs.c)
911

1012
zephyr_sources_ifdef(CONFIG_BT_TPS tps.c)

subsys/bluetooth/services/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ rsource "Kconfig.dis"
1010

1111
rsource "Kconfig.cts"
1212

13+
rsource "Kconfig.ets"
14+
1315
rsource "Kconfig.hrs"
1416

1517
rsource "Kconfig.tps"

0 commit comments

Comments
 (0)