Skip to content

Commit bc1a75b

Browse files
refactor: add new datetime subsystem (#3)
* docs: add library level docstrings * refactor: create datetime subsystem to organize rtc * chore: replace tabs with spaces * refactor: rename subsystems' logs * refactor: use devicetwin for time tracking * refactor: rename bluetooth stack interface * refactor: remove unneccessary DIS info
1 parent 472df6f commit bc1a75b

File tree

21 files changed

+461
-223
lines changed

21 files changed

+461
-223
lines changed

prj.conf

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ CONFIG_BT_DEVICE_NAME="ZephyrWatch"
3232
# Bluetooth GATT Device Information Service Configuration
3333
CONFIG_BT_DIS=y
3434
CONFIG_BT_DIS_PNP=n
35-
CONFIG_BT_DIS_MODEL="ZephyrWatch_001"
36-
CONFIG_BT_DIS_MANUF="electricalgorithm"
37-
CONFIG_BT_DIS_SERIAL_NUMBER=y
35+
CONFIG_BT_DIS_MODEL="ZephyrWatch"
36+
CONFIG_BT_DIS_MANUF="gyokhan.com"
37+
CONFIG_BT_DIS_SERIAL_NUMBER=n
3838
CONFIG_BT_DIS_FW_REV=y
39-
CONFIG_BT_DIS_HW_REV=y
40-
CONFIG_BT_DIS_SW_REV=y
41-
CONFIG_BT_DIS_SERIAL_NUMBER_STR="0.0.1"
42-
CONFIG_BT_DIS_FW_REV_STR="0.0.1"
43-
CONFIG_BT_DIS_HW_REV_STR="0.0.1"
44-
CONFIG_BT_DIS_SW_REV_STR="0.0.1"
39+
CONFIG_BT_DIS_HW_REV=n
40+
CONFIG_BT_DIS_SW_REV=n
41+
CONFIG_BT_DIS_FW_REV_STR="0.1.0"
42+
# CONFIG_BT_DIS_SERIAL_NUMBER_STR="0.0.0"
43+
# CONFIG_BT_DIS_HW_REV_STR="0.0.0"
44+
# CONFIG_BT_DIS_SW_REV_STR="0.0.0"
4545
CONFIG_BT_DIS_SETTINGS=y
4646
CONFIG_BT_DIS_STR_MAX=21
4747

src/bluetooth/infrastructure.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
/** Bluetooth infrastructure for Zephyr-based devices.
2+
* This file manages Bluetooth functionality, including advertising and connection handling.
3+
*
4+
* @license: GNU v3
5+
* @maintainer: electricalgorithm @ github
6+
*/
7+
18
#include <zephyr/settings/settings.h>
29
#include <zephyr/bluetooth/gatt.h>
310
#include <zephyr/logging/log.h>
411

5-
LOG_MODULE_REGISTER(ZephyrWatch_BluetoothInfra, LOG_LEVEL_INF);
12+
LOG_MODULE_REGISTER(ZephyrWatch_BLE, LOG_LEVEL_INF);
613

714
static const struct bt_data m_ad[] = {
815
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
@@ -32,7 +39,7 @@ BT_CONN_CB_DEFINE(conn_callbacks) = {
3239
};
3340

3441
/* The API function to enable Bluetooth and start advertisement. */
35-
uint8_t enable_bluetooth_and_start_advertisement() {
42+
uint8_t enable_bluetooth_subsystem() {
3643
int err;
3744

3845
err = bt_enable(NULL);
@@ -57,7 +64,7 @@ uint8_t enable_bluetooth_and_start_advertisement() {
5764
return 0;
5865
}
5966

60-
uint8_t disable_bluetooth_and_stop_advertisement() {
67+
uint8_t disable_bluetooth_subsystem() {
6168
int err;
6269

6370
err = bt_le_adv_stop();

src/bluetooth/infrastructure.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
/** Bluetooth Subsystem for ZephyrWatch
2+
* This interface manages Bluetooth functionality, including advertising and connection handling.
3+
*
4+
* @license: GNU v3
5+
* @maintainer: electricalgorithm @ github
6+
*/
7+
18
#ifndef BLUETOOTH_INFRASTRUCTURE_H_
29
#define BLUETOOTH_INFRASTRUCTURE_H_
310

@@ -9,8 +16,8 @@ extern "C" {
916
#include <zephyr/bluetooth/gatt.h>
1017
#include <zephyr/logging/log.h>
1118

12-
uint8_t enable_bluetooth_and_start_advertisement();
13-
uint8_t disable_bluetooth_and_stop_advertisement();
19+
uint8_t enable_bluetooth_subsystem();
20+
uint8_t disable_bluetooth_subsystem();
1421

1522
#ifdef __cplusplus
1623
}

src/bluetooth/services/current_time_service.c

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1+
/** Current Time Service (CTS) implementation for handling time synchronization via Bluetooth GATT.
2+
* This service allows devices to synchronize their time using the Current Time Service protocol.
3+
*
4+
* @license: GNU v3
5+
* @maintainer: electricalgorithm @ github
6+
*/
7+
18
#include <zephyr/sys/byteorder.h>
29
#include <zephyr/logging/log.h>
310
#include <zephyr/bluetooth/gatt.h>
411
#include <string.h>
512

613
#include "current_time_service.h"
7-
#include "timeutils/timeutils.h"
14+
#include "datetime/datetime.h"
815
#include "devicetwin/devicetwin.h"
916

10-
LOG_MODULE_REGISTER(ZephyrWatch_CurrentTimeService, LOG_LEVEL_INF);
17+
LOG_MODULE_REGISTER(ZephyrWatch_BLE_CTS, LOG_LEVEL_INF);
1118

1219
/* Current Time Service Write Callback */
1320
static ssize_t m_time_write_callback(
@@ -26,24 +33,19 @@ static ssize_t m_time_write_callback(
2633

2734
// Extract the UNIX timestamp from the buffer (assuming little-endian)
2835
uint32_t unix_timestamp = sys_le32_to_cpu(*(uint32_t *)buf);
29-
LOG_INF("Received UNIX timestamp: %u", unix_timestamp);
36+
LOG_DBG("Received UNIX timestamp: %u", unix_timestamp);
3037

3138
// Get the device twin instance to get the UTC zone
3239
device_twin_t *device_twin = get_device_twin_instance();
3340
if (device_twin == NULL) {
3441
LOG_ERR("Failed to get device twin instance.");
3542
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
3643
}
44+
device_twin->unix_time = unix_timestamp;
45+
trigger_ui_update();
3746

38-
// Convert UNIX timestamp to local time using the device's UTC zone
39-
utc_time_t local_time = unix_to_localtime(unix_timestamp, device_twin->utc_zone);
40-
41-
// Update the device twin's current time with local time
42-
device_twin->current_time = local_time;
43-
44-
// Update the global unix time used by the UI system
45-
update_global_unix_time(unix_timestamp);
46-
47+
// Convert UNIX timestamp to local time using the device's UTC zone to print.
48+
datetime_t local_time = unix_to_localtime(unix_timestamp, device_twin->utc_zone);
4749
LOG_INF("Current time updated to local time: %04d-%02d-%02d %02d:%02d:%02d (UTC%+d)",
4850
local_time.year, local_time.month, local_time.day,
4951
local_time.hour, local_time.minute, local_time.second,

src/bluetooth/services/current_time_service.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
/** Current Time Service (CTS) interface for handling time synchronization via Bluetooth GATT.
2+
* This service allows devices to synchronize their time using the Current Time Service protocol.
3+
*
4+
* @license: GNU v3
5+
* @maintainer: electricalgorithm @ github
6+
*/
7+
18
#ifndef CURRENT_TIME_SERVICE_H
29
#define CURRENT_TIME_SERVICE_H
310

@@ -8,10 +15,10 @@ extern "C" {
815
#include <zephyr/sys/byteorder.h>
916
#include <zephyr/logging/log.h>
1017
#include <zephyr/bluetooth/gatt.h>
11-
#include "timeutils/timeutils.h"
18+
#include "datetime/datetime.h"
1219

13-
/* Function to update the global unix time - to be implemented in main.c */
14-
extern void update_global_unix_time(uint32_t unix_timestamp);
20+
/* A function to trigger UI updates - to be implemented in main.c */
21+
extern void trigger_ui_update();
1522

1623
#ifdef __cplusplus
1724
}

src/datetime/datetime.c

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/** Datetime Subsystem for ZephyrWatch
2+
* It is a real-time clock implementation based on real-time counters for ESP32.
3+
*
4+
* @license: GNU v3
5+
* @maintainer: electricalgorithm @ github
6+
*/
7+
8+
#include <zephyr/kernel.h>
9+
#include <zephyr/logging/log.h>
10+
#include <zephyr/device.h>
11+
#include <zephyr/drivers/counter.h>
12+
13+
#include "devicetwin/devicetwin.h"
14+
#include "datetime/datetime.h"
15+
16+
// Configuration for counter alarm.
17+
#define ALARM_INTERVAL_US 1000000
18+
#define ALARM_CHANNEL_ID 0
19+
20+
/* Register a logger for this library. */
21+
LOG_MODULE_REGISTER(ZephyrWatch_Datetime, LOG_LEVEL_INF);
22+
23+
/* Global unix_time variable to store datetime. */
24+
uint32_t unix_time = 1748554674;
25+
26+
/* Disable flag to not set alarm again in ISR.
27+
* 0: Set alarm again.
28+
* !: Do not set alarm again.
29+
*/
30+
static uint8_t reset_alarm = 0;
31+
32+
/* Global alarm configuration structure - must persist for ISR access */
33+
static struct counter_alarm_cfg alarm_cfg;
34+
35+
/* Prototype definition of internal static functions and variables */
36+
static const uint16_t days_in_month[] = {
37+
31, 28, 31, 30, 31, 30,
38+
31, 31, 30, 31, 30, 31
39+
};
40+
static bool is_leap_year(uint16_t year);
41+
static uint8_t calc_weekday(uint32_t days_since_epoch);
42+
// static datetime_t unix_to_localtime(int32_t timestamp, int8_t utc_offset_hours);
43+
// static datetime_t unix_to_utc(uint32_t timestamp);
44+
45+
/* RTC_ISR
46+
* Interrupt service routine for alarm with real-time counters. This ISR is executed every
47+
* seconds. The user_data is an counter_alarm_cfg object. It updates static unix_time variable.
48+
*/
49+
void rtc_isr(const struct device *dev, uint8_t channel_id, uint32_t ticks, void *user_data) {
50+
// Cast alarm config from user data.
51+
struct counter_alarm_cfg *alarm_cfg = user_data;
52+
53+
// Reset alarm if flag is set.
54+
if (!reset_alarm) {
55+
alarm_cfg->ticks = counter_us_to_ticks(dev, ALARM_INTERVAL_US);
56+
counter_set_channel_alarm(dev, ALARM_CHANNEL_ID, alarm_cfg);
57+
}
58+
59+
// Update device's current time.
60+
device_twin_t *device_twin = get_device_twin_instance();
61+
device_twin->unix_time = device_twin->unix_time + 1;
62+
}
63+
64+
/* ENABLE_DATETIME_SUBSYSTEM
65+
* This function sets the soft real-time clock to track the time. It implements the counters
66+
* with one second alarm to update a global variable.
67+
*/
68+
int enable_datetime_subsystem() {
69+
int ret;
70+
71+
// Set the real time counter to trigger an callback every second.
72+
const struct device *real_time_counter = DEVICE_DT_GET(DT_NODELABEL(rtc_timer));
73+
if (!device_is_ready(real_time_counter)) {
74+
LOG_ERR("Real time counter device is not ready.");
75+
return -ENODEV;
76+
}
77+
LOG_DBG("Real time counter device is ready.");
78+
79+
// Configure real time counter to track tine.
80+
ret = counter_start(real_time_counter);
81+
if (ret) {
82+
LOG_ERR("Failed to start real time counter (ret %d).", ret);
83+
return ret;
84+
}
85+
LOG_DBG("Real time counter started successfully.");
86+
87+
// Configure the global alarm structure.
88+
alarm_cfg.flags = 0;
89+
alarm_cfg.ticks = counter_us_to_ticks(real_time_counter, ALARM_INTERVAL_US);
90+
alarm_cfg.callback = rtc_isr;
91+
alarm_cfg.user_data = &alarm_cfg;
92+
93+
ret = counter_set_channel_alarm(real_time_counter, ALARM_CHANNEL_ID, &alarm_cfg);
94+
if (ret) {
95+
LOG_ERR("Failed to set channel alarm (ret %d).", ret);
96+
return ret;
97+
}
98+
LOG_DBG("Channel alarm set successfully.");
99+
100+
return 0;
101+
}
102+
103+
/* DISABLE_DATETIME_SUBSYSTEM
104+
* Stops the real-time counter and its alarm to disable the datetime subsystem.
105+
*/
106+
int disable_datetime_subsystem() {
107+
int ret;
108+
109+
// Set the real time counter to trigger an callback every second.
110+
const struct device *real_time_counter = DEVICE_DT_GET(DT_NODELABEL(rtc_timer));
111+
if (!device_is_ready(real_time_counter)) {
112+
LOG_ERR("Real time counter device is not ready.");
113+
return -ENODEV;
114+
}
115+
LOG_DBG("Real time counter device is ready.");
116+
117+
// Disable the alarm first.
118+
reset_alarm = 1;
119+
LOG_DBG("Reset flag is cleared.");
120+
121+
// Stop real time counter to track tine.
122+
ret = counter_stop(real_time_counter);
123+
if (ret) {
124+
LOG_ERR("Failed to stop real time counter (ret %d).", ret);
125+
return ret;
126+
}
127+
LOG_DBG("Real time counter stopped successfully.");
128+
129+
return 0;
130+
}
131+
132+
/* GET_CURRENT_UNIX_TIME
133+
* Return the UNIX epochs of the current time.
134+
*/
135+
uint32_t get_current_unix_time() {
136+
return unix_time;
137+
}
138+
139+
/* SET_CURRENT_UNIX_TIME
140+
* Set the current time with UNIX epoch.
141+
*/
142+
int set_current_unix_time(uint32_t new_time) {
143+
unix_time = new_time;
144+
return 0;
145+
}
146+
147+
/* GET_CURRENT_LOCAL_TIME
148+
* Return the current time in datetime_t object in local time zone.
149+
*/
150+
datetime_t get_current_local_time(int8_t utc_offset_hours) {
151+
return unix_to_localtime(unix_time, utc_offset_hours);
152+
}
153+
154+
/* UNIX_TO_LOCALTIME
155+
* Converts Unix time to local time using UTC offset in hours (e.g., +2 or -5)
156+
*/
157+
datetime_t unix_to_localtime(int32_t timestamp, int8_t utc_offset_hours) {
158+
datetime_t utc;
159+
160+
// Apply time zone offset
161+
int32_t adjusted = timestamp + (utc_offset_hours * 3600);
162+
163+
// Handle negative timestamps (before 1970)
164+
if (adjusted < 0) {
165+
// Optional: clamp or handle negative time
166+
adjusted = 0;
167+
}
168+
169+
uint32_t seconds = (uint32_t)adjusted;
170+
171+
// Time part
172+
utc.second = seconds % 60;
173+
seconds /= 60;
174+
utc.minute = seconds % 60;
175+
seconds /= 60;
176+
utc.hour = seconds % 24;
177+
uint32_t days = seconds / 24;
178+
179+
utc.weekday = calc_weekday(days);
180+
181+
// Date part
182+
uint16_t year = 1970;
183+
while (1) {
184+
uint16_t days_in_year = is_leap_year(year) ? 366 : 365;
185+
if (days < days_in_year) break;
186+
days -= days_in_year;
187+
year++;
188+
}
189+
utc.year = year;
190+
191+
uint8_t month = 0;
192+
while (1) {
193+
uint16_t dim = days_in_month[month];
194+
if (month == 1 && is_leap_year(year)) dim++;
195+
if (days < dim) break;
196+
days -= dim;
197+
month++;
198+
}
199+
utc.month = month + 1;
200+
utc.day = days + 1;
201+
202+
return utc;
203+
}
204+
205+
/* UNIX_TO_UTC
206+
* Converts Unix time to UTC.
207+
*/
208+
datetime_t unix_to_utc(uint32_t timestamp) {
209+
return unix_to_localtime((int32_t)timestamp, 0);
210+
}
211+
212+
/** **************** **/
213+
/** STATIC FUNCTIONS **/
214+
/** **************** **/
215+
216+
/* IS_LEAP_YEAR
217+
* Check if the year is a leap year. Internal purposes.
218+
*/
219+
static bool is_leap_year(uint16_t year) {
220+
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
221+
}
222+
223+
/* CALC_WEEKDAY
224+
* Calculate the day of the given days after epoch.
225+
*/
226+
static uint8_t calc_weekday(uint32_t days_since_epoch) {
227+
return (days_since_epoch + 4) % 7; // 1970-01-01 = Thursday
228+
}

0 commit comments

Comments
 (0)