Skip to content

Commit 69c7c40

Browse files
committed
samples: bluetooth: add peripheral ets sample
Added elapsed time service (ETS) GATT service sample. The sample allows the user to set the system realtime clock on the board and read the time. Signed-off-by: Dipak Shetty <[email protected]>
1 parent 4bf5116 commit 69c7c40

File tree

6 files changed

+327
-0
lines changed

6 files changed

+327
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(peripheral_ets)
7+
8+
target_sources(app PRIVATE src/main.c)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
.. zephyr:code-sample:: ble_peripheral_ets
2+
:name: Elapsed Time Service (ETS) Peripheral
3+
:relevant-api: bt_ets bluetooth
4+
5+
Expose an Elapsed Time Service (ETS) GATT Service.
6+
7+
Overview
8+
********
9+
10+
Similar to the :zephyr:code-sample:`ble_peripheral` sample, except that this
11+
application specifically exposes the ETS (Elapsed Time Service) GATT Service.
12+
13+
The sample demonstrates time-of-day mode using the system realtime clock
14+
(``SYS_CLOCK_REALTIME``). It supports both UTC time (default) and local time
15+
with TZ/DST offset (via overlay). The implementation uses ETS helper APIs for
16+
all time conversions between Unix epoch (1970) and ETS epoch (2000).
17+
18+
19+
Requirements
20+
************
21+
22+
* BlueZ running on the host, or
23+
* A board with Bluetooth LE support
24+
25+
Building and Running
26+
********************
27+
28+
This sample can be found under :zephyr_file:`samples/bluetooth/peripheral_ets`
29+
in the Zephyr tree.
30+
31+
Building with UTC time mode (default)
32+
--------------------------------------
33+
34+
.. zephyr-app-commands::
35+
:zephyr-app: samples/bluetooth/peripheral_ets
36+
:board: <board>
37+
:goals: build flash
38+
39+
Building with local time mode
40+
------------------------------
41+
42+
.. zephyr-app-commands::
43+
:zephyr-app: samples/bluetooth/peripheral_ets
44+
:board: <board>
45+
:goals: build flash
46+
:gen-args: -DEXTRA_CONF_FILE=overlay-local-time.conf
47+
48+
See :zephyr:code-sample-category:`bluetooth` samples for details.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
#
3+
# Overlay for local time mode with TZ/DST offset support
4+
# Build with: west build -b <board> -- -DEXTRA_CONF_FILE=overlay-local-time.conf
5+
6+
# Use local time instead of UTC
7+
CONFIG_BT_ETS_SUPPORT_UTC=n
8+
CONFIG_BT_ETS_SUPPORT_LOCAL_TIME=y
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
# Bluetooth configuration
4+
CONFIG_BT=y
5+
CONFIG_BT_PERIPHERAL=y
6+
CONFIG_BT_DEVICE_NAME="ETS Peripheral"
7+
CONFIG_BT_HCI_ERR_TO_STR=y
8+
9+
# Elapsed Time Service
10+
CONFIG_BT_ETS=y
11+
CONFIG_BT_ETS_CURRENT_ELAPSED_TIME_WRITABLE=y
12+
CONFIG_BT_ETS_SUPPORT_TIME_OF_DAY=y
13+
CONFIG_BT_ETS_RESOLUTION_1_MS=y
14+
CONFIG_BT_ETS_SUPPORT_UTC=y
15+
CONFIG_BT_ETS_SUPPORT_TZ_DST=y
16+
CONFIG_BT_ETS_HELPER_API=y
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
sample:
2+
name: Bluetooth Peripheral ETS
3+
description: Demonstrates the ETS (Elapsed Time Service) GATT Service
4+
tests:
5+
sample.bluetooth.peripheral_ets:
6+
harness: bluetooth
7+
platform_allow:
8+
- qemu_cortex_m3
9+
- qemu_x86
10+
- frdm_rw612
11+
tags: bluetooth
12+
integration_platforms:
13+
- qemu_cortex_m3
14+
sample.bluetooth.peripheral_ets.local_time:
15+
harness: bluetooth
16+
platform_allow:
17+
- qemu_cortex_m3
18+
- qemu_x86
19+
- frdm_rw612
20+
integration_platforms:
21+
- qemu_cortex_m3
22+
extra_args: EXTRA_CONF_FILE=overlay-local-time.conf
23+
tags: bluetooth
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Copyright (c) 2025 Dipak Shetty
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <time.h>
8+
9+
#include <zephyr/bluetooth/bluetooth.h>
10+
#include <zephyr/bluetooth/conn.h>
11+
#include <zephyr/bluetooth/gatt.h>
12+
#include <zephyr/bluetooth/hci.h>
13+
#include <zephyr/bluetooth/services/ets.h>
14+
#include <zephyr/kernel.h>
15+
#include <zephyr/sys/clock.h>
16+
17+
/* Clock status: indicates if time needs to be set */
18+
static uint8_t clock_status = BT_ETS_CLOCK_STATUS_NEEDS_SET;
19+
/* Timezone/DST offset in 15-minute units */
20+
static int8_t tz_dst_offset;
21+
22+
/* ETS epoch: 2000-01-01 00:00:00 */
23+
#define ETS_EPOCH_UNIX_MS 946684800000LL
24+
25+
/* Advertising data */
26+
static const struct bt_data ad[] = {
27+
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
28+
BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_ETS_VAL)),
29+
};
30+
31+
static const struct bt_data sd[] = {
32+
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
33+
};
34+
35+
static void advertise(struct k_work *work)
36+
{
37+
int err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
38+
39+
if (err) {
40+
printk("Advertising failed to start (err %d)\n", err);
41+
return;
42+
}
43+
44+
printk("Advertising successfully started\n");
45+
}
46+
47+
K_WORK_DEFINE(advertise_work, advertise);
48+
49+
static void connected(struct bt_conn *conn, uint8_t err)
50+
{
51+
char addr[BT_ADDR_LE_STR_LEN];
52+
53+
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
54+
55+
if (err) {
56+
printk("Failed to connect to %s (err %u)\n", addr, err);
57+
return;
58+
}
59+
60+
printk("Connected: %s\n", addr);
61+
}
62+
63+
static void disconnected(struct bt_conn *conn, uint8_t reason)
64+
{
65+
char addr[BT_ADDR_LE_STR_LEN];
66+
67+
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
68+
printk("Disconnected: %s, reason 0x%02x %s\n", addr, reason, bt_hci_err_to_str(reason));
69+
}
70+
71+
static void on_conn_recycled(void)
72+
{
73+
k_work_submit(&advertise_work);
74+
}
75+
76+
BT_CONN_CB_DEFINE(conn_callbacks) = {
77+
.connected = connected,
78+
.disconnected = disconnected,
79+
.recycled = on_conn_recycled,
80+
};
81+
82+
/* Read current time and convert to ETS format */
83+
static int read_elapsed_time(struct bt_ets_elapsed_time *elapsed_time)
84+
{
85+
struct timespec ts;
86+
int64_t unix_ms;
87+
int err;
88+
89+
/* Get current wall clock time (Unix epoch 1970-01-01) */
90+
err = sys_clock_gettime(SYS_CLOCK_REALTIME, &ts);
91+
if (err) {
92+
printk("Failed to get realtime clock: %d\n", err);
93+
return err;
94+
}
95+
96+
/* Convert to milliseconds */
97+
unix_ms = ts.tv_sec * MSEC_PER_SEC + ts.tv_nsec / NSEC_PER_MSEC;
98+
99+
/* If clock hasn't been set yet, use ETS epoch as default */
100+
if (unix_ms < ETS_EPOCH_UNIX_MS) {
101+
unix_ms = ETS_EPOCH_UNIX_MS;
102+
}
103+
104+
/* Convert Unix time to ETS format */
105+
err = bt_ets_time_from_unix_ms(elapsed_time, unix_ms, BT_ETS_TIME_SOURCE_MANUAL,
106+
tz_dst_offset);
107+
if (err) {
108+
printk("Failed to convert time: %d\n", err);
109+
return err;
110+
}
111+
112+
return 0;
113+
}
114+
115+
/* Handle time write from client */
116+
static enum bt_ets_write_result write_elapsed_time(const struct bt_ets_elapsed_time *elapsed_time)
117+
{
118+
struct timespec ts;
119+
int64_t unix_ms;
120+
int err;
121+
122+
/* Convert ETS time to Unix milliseconds (UTC) */
123+
err = bt_ets_time_to_unix_ms(elapsed_time, &unix_ms);
124+
if (err) {
125+
printk("Failed to decode time: %d\n", err);
126+
return BT_ETS_WRITE_INCORRECT_FORMAT;
127+
}
128+
129+
/* Set the system realtime clock */
130+
ts.tv_sec = unix_ms / MSEC_PER_SEC;
131+
ts.tv_nsec = (unix_ms % MSEC_PER_SEC) * NSEC_PER_MSEC;
132+
133+
err = sys_clock_settime(SYS_CLOCK_REALTIME, &ts);
134+
if (err) {
135+
printk("Failed to set realtime clock: %d\n", err);
136+
return BT_ETS_WRITE_OUT_OF_RANGE;
137+
}
138+
139+
/* Store timezone/DST offset if provided */
140+
tz_dst_offset = elapsed_time->tz_dst_offset;
141+
142+
/* Clear "clock needs to be set" flag after successful write */
143+
clock_status &= ~BT_ETS_CLOCK_STATUS_NEEDS_SET;
144+
145+
/* Log the time update with human-readable format */
146+
time_t unix_sec = unix_ms / MSEC_PER_SEC;
147+
struct tm const *timeinfo = gmtime(&unix_sec);
148+
char time_str[64];
149+
150+
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", timeinfo);
151+
printk("Time updated: %s UTC (source: %d)\n", time_str, elapsed_time->time_sync_src);
152+
153+
return BT_ETS_WRITE_SUCCESS;
154+
}
155+
156+
static int read_clock_status(uint8_t *status)
157+
{
158+
*status = clock_status;
159+
return 0;
160+
}
161+
162+
static int read_clock_capabilities(uint8_t *capabilities)
163+
{
164+
/* This sample does not autonomously update time/timezone */
165+
*capabilities = 0;
166+
return 0;
167+
}
168+
169+
static void indication_changed(bool enabled)
170+
{
171+
printk("ETS indications %s\n", enabled ? "enabled" : "disabled");
172+
}
173+
174+
static const struct bt_ets_cb ets_callbacks = {
175+
.read_elapsed_time = read_elapsed_time,
176+
.write_elapsed_time = write_elapsed_time,
177+
.read_clock_status = read_clock_status,
178+
.read_clock_capabilities = read_clock_capabilities,
179+
.indication_changed = indication_changed,
180+
};
181+
182+
static void bt_ready(int err)
183+
{
184+
if (err) {
185+
printk("Bluetooth init failed (err %d)\n", err);
186+
return;
187+
}
188+
189+
k_work_submit(&advertise_work);
190+
}
191+
192+
int main(void)
193+
{
194+
int err;
195+
196+
printk("Bluetooth Elapsed Time Service Peripheral Sample\n");
197+
198+
#if defined(CONFIG_BT_ETS_SUPPORT_UTC)
199+
printk("Mode: UTC time\n");
200+
#elif defined(CONFIG_BT_ETS_SUPPORT_LOCAL_TIME)
201+
printk("Mode: Local time\n");
202+
#endif
203+
204+
#if defined(CONFIG_BT_ETS_SUPPORT_TZ_DST)
205+
printk("TZ/DST offset: Supported\n");
206+
#endif
207+
208+
/* Initialize Bluetooth */
209+
err = bt_enable(bt_ready);
210+
if (err) {
211+
printk("Bluetooth init failed (err %d)\n", err);
212+
return 0;
213+
}
214+
215+
/* Initialize ETS */
216+
err = bt_ets_init(&ets_callbacks);
217+
if (err) {
218+
printk("ETS init failed (err %d)\n", err);
219+
return 0;
220+
}
221+
222+
printk("ETS initialized successfully\n");
223+
return 0;
224+
}

0 commit comments

Comments
 (0)