| 
 | 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_ */  | 
0 commit comments