1 /** @file
2 * @brief HRS Service sample
3 */
4
5 /*
6 * Copyright (c) 2016 Intel Corporation
7 *
8 * SPDX-License-Identifier: Apache-2.0
9 */
10
11 #include <zephyr/types.h>
12 #include <stddef.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <zephyr/kernel.h>
16 #include <zephyr/init.h>
17 #include <zephyr/sys/check.h>
18
19 #include <zephyr/bluetooth/bluetooth.h>
20 #include <zephyr/bluetooth/hci.h>
21 #include <zephyr/bluetooth/conn.h>
22 #include <zephyr/bluetooth/uuid.h>
23 #include <zephyr/bluetooth/gatt.h>
24 #include <zephyr/bluetooth/services/hrs.h>
25
26 #define LOG_LEVEL CONFIG_BT_HRS_LOG_LEVEL
27 #include <zephyr/logging/log.h>
28 LOG_MODULE_REGISTER(hrs);
29
30
31 #define GATT_PERM_READ_MASK (BT_GATT_PERM_READ | \
32 BT_GATT_PERM_READ_ENCRYPT | \
33 BT_GATT_PERM_READ_AUTHEN)
34 #define GATT_PERM_WRITE_MASK (BT_GATT_PERM_WRITE | \
35 BT_GATT_PERM_WRITE_ENCRYPT | \
36 BT_GATT_PERM_WRITE_AUTHEN)
37
38 #ifndef CONFIG_BT_HRS_DEFAULT_PERM_RW_AUTHEN
39 #define CONFIG_BT_HRS_DEFAULT_PERM_RW_AUTHEN 0
40 #endif
41 #ifndef CONFIG_BT_HRS_DEFAULT_PERM_RW_ENCRYPT
42 #define CONFIG_BT_HRS_DEFAULT_PERM_RW_ENCRYPT 0
43 #endif
44 #ifndef CONFIG_BT_HRS_DEFAULT_PERM_RW
45 #define CONFIG_BT_HRS_DEFAULT_PERM_RW 0
46 #endif
47
48 /**
49 * @brief GATT ATTR Error that should be returned in case
50 * HRS Control point request is not supported.
51 */
52 #define BT_HRS_ATT_ERR_CONTROL_POINT_NOT_SUPPORTED 0x80
53
54 #define HRS_GATT_PERM_DEFAULT ( \
55 CONFIG_BT_HRS_DEFAULT_PERM_RW_AUTHEN ? \
56 (BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN) : \
57 CONFIG_BT_HRS_DEFAULT_PERM_RW_ENCRYPT ? \
58 (BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT) : \
59 (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)) \
60
61 static uint8_t hrs_blsc;
62 static sys_slist_t hrs_cbs = SYS_SLIST_STATIC_INIT(&hrs_cbs);
63
hrmc_ccc_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)64 static void hrmc_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
65 {
66 ARG_UNUSED(attr);
67
68 struct bt_hrs_cb *listener;
69
70 bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
71
72 LOG_INF("HRS notifications %s", notif_enabled ? "enabled" : "disabled");
73
74 SYS_SLIST_FOR_EACH_CONTAINER(&hrs_cbs, listener, _node) {
75 if (listener->ntf_changed) {
76 listener->ntf_changed(notif_enabled);
77 }
78 }
79 }
80
read_blsc(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)81 static ssize_t read_blsc(struct bt_conn *conn, const struct bt_gatt_attr *attr,
82 void *buf, uint16_t len, uint16_t offset)
83 {
84 return bt_gatt_attr_read(conn, attr, buf, len, offset, &hrs_blsc,
85 sizeof(hrs_blsc));
86 }
87
ctrl_point_write(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)88 static ssize_t ctrl_point_write(struct bt_conn *conn, const struct bt_gatt_attr *attr,
89 const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
90 {
91 int err = -ENOTSUP;
92 struct bt_hrs_cb *listener;
93
94 LOG_INF("HRS CTRL Point Written %d", len);
95
96 SYS_SLIST_FOR_EACH_CONTAINER(&hrs_cbs, listener, _node) {
97 if (listener->ctrl_point_write) {
98 err = listener->ctrl_point_write(*((uint8_t *)buf));
99 /* If we get an error other than ENOTSUP then immediately
100 * break the loop and return a generic gatt error, assuming this
101 * listener supports this request code, but failed to serve it
102 */
103 if ((err != 0) && (err != -ENOTSUP)) {
104 return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
105 }
106 }
107 }
108 if (err) {
109 return BT_GATT_ERR(BT_HRS_ATT_ERR_CONTROL_POINT_NOT_SUPPORTED);
110 } else {
111 return len;
112 }
113 }
114
115 /* Heart Rate Service Declaration */
116 BT_GATT_SERVICE_DEFINE(hrs_svc,
117 BT_GATT_PRIMARY_SERVICE(BT_UUID_HRS),
118 BT_GATT_CHARACTERISTIC(BT_UUID_HRS_MEASUREMENT, BT_GATT_CHRC_NOTIFY,
119 BT_GATT_PERM_NONE, NULL, NULL, NULL),
120 BT_GATT_CCC(hrmc_ccc_cfg_changed,
121 HRS_GATT_PERM_DEFAULT),
122 BT_GATT_CHARACTERISTIC(BT_UUID_HRS_BODY_SENSOR, BT_GATT_CHRC_READ,
123 HRS_GATT_PERM_DEFAULT & GATT_PERM_READ_MASK,
124 read_blsc, NULL, NULL),
125 BT_GATT_CHARACTERISTIC(BT_UUID_HRS_CONTROL_POINT, BT_GATT_CHRC_WRITE,
126 HRS_GATT_PERM_DEFAULT & GATT_PERM_WRITE_MASK,
127 NULL, ctrl_point_write, NULL),
128 );
129
hrs_init(void)130 static int hrs_init(void)
131 {
132
133 hrs_blsc = 0x01;
134
135 return 0;
136 }
137
bt_hrs_cb_register(struct bt_hrs_cb * cb)138 int bt_hrs_cb_register(struct bt_hrs_cb *cb)
139 {
140 CHECKIF(cb == NULL) {
141 return -EINVAL;
142 }
143
144 sys_slist_append(&hrs_cbs, &cb->_node);
145
146 return 0;
147 }
148
bt_hrs_cb_unregister(struct bt_hrs_cb * cb)149 int bt_hrs_cb_unregister(struct bt_hrs_cb *cb)
150 {
151 CHECKIF(cb == NULL) {
152 return -EINVAL;
153 }
154
155 if (!sys_slist_find_and_remove(&hrs_cbs, &cb->_node)) {
156 return -ENOENT;
157 }
158
159 return 0;
160 }
161
bt_hrs_notify(uint16_t heartrate)162 int bt_hrs_notify(uint16_t heartrate)
163 {
164 int rc;
165 static uint8_t hrm[2];
166
167 hrm[0] = 0x06; /* uint8, sensor contact */
168 hrm[1] = heartrate;
169
170 rc = bt_gatt_notify(NULL, &hrs_svc.attrs[1], &hrm, sizeof(hrm));
171
172 return rc == -ENOTCONN ? 0 : rc;
173 }
174
175 SYS_INIT(hrs_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
176