Skip to content

Commit 1d9adc9

Browse files
committed
bluetooth: services: add Elapsed Time Service support
Added support for elapsed time service (ETS). Signed-off-by: Dipak Shetty <[email protected]>
1 parent aefc8c5 commit 1d9adc9

File tree

5 files changed

+1028
-0
lines changed

5 files changed

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