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