1 /*
2 * Copyright (c) 2022 Codecoup
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <errno.h>
8 #include <stdint.h>
9 #include <zephyr/sys/check.h>
10 #include <zephyr/logging/log.h>
11
12 LOG_MODULE_REGISTER(bt_ias_client, CONFIG_BT_IAS_CLIENT_LOG_LEVEL);
13
14 #include <zephyr/bluetooth/gatt.h>
15 #include <zephyr/bluetooth/conn.h>
16 #include <zephyr/bluetooth/services/ias.h>
17
18 enum {
19 IAS_DISCOVER_IN_PROGRESS,
20
21 IAS_NUM_FLAGS, /* keep as last */
22 };
23
24 struct bt_ias_client {
25 /* Handle for alert writes */
26 uint16_t alert_level_handle;
27
28 /** Internal flags **/
29 ATOMIC_DEFINE(flags, IAS_NUM_FLAGS);
30
31 /* Gatt discover procedure parameters */
32 struct bt_gatt_discover_params discover;
33 };
34
35 static const struct bt_uuid *alert_lvl_uuid = BT_UUID_ALERT_LEVEL;
36 static const struct bt_uuid *ias_uuid = BT_UUID_IAS;
37
38 static const struct bt_ias_client_cb *ias_client_cb;
39 static struct bt_ias_client client_list[CONFIG_BT_MAX_CONN];
40
client_by_conn(struct bt_conn * conn)41 static struct bt_ias_client *client_by_conn(struct bt_conn *conn)
42 {
43 return &client_list[bt_conn_index(conn)];
44 }
45
client_cleanup(struct bt_ias_client * ias_client)46 static void client_cleanup(struct bt_ias_client *ias_client)
47 {
48 (void)memset(ias_client, 0, sizeof(*ias_client));
49 }
50
discover_complete(struct bt_conn * conn,int err)51 static void discover_complete(struct bt_conn *conn, int err)
52 {
53 LOG_DBG("conn %p", (void *)conn);
54
55 if (err) {
56 client_cleanup(client_by_conn(conn));
57 LOG_DBG("Discover failed (err %d\n)", err);
58 }
59
60 if (ias_client_cb != NULL && ias_client_cb->discover != NULL) {
61 ias_client_cb->discover(conn, err);
62 }
63 }
64
bt_ias_client_alert_write(struct bt_conn * conn,enum bt_ias_alert_lvl lvl)65 int bt_ias_client_alert_write(struct bt_conn *conn, enum bt_ias_alert_lvl lvl)
66 {
67 int err;
68 uint8_t lvl_u8;
69
70 CHECKIF(conn == NULL) {
71 return -ENOTCONN;
72 }
73
74 if (client_by_conn(conn)->alert_level_handle == 0) {
75 return -EINVAL;
76 }
77
78 lvl_u8 = (uint8_t)lvl;
79
80 if (lvl_u8 != BT_IAS_ALERT_LVL_NO_ALERT &&
81 lvl_u8 != BT_IAS_ALERT_LVL_MILD_ALERT &&
82 lvl_u8 != BT_IAS_ALERT_LVL_HIGH_ALERT) {
83 LOG_ERR("Invalid alert value: %u", lvl_u8);
84 return -EINVAL;
85 }
86
87 err = bt_gatt_write_without_response(conn,
88 client_by_conn(conn)->alert_level_handle,
89 &lvl_u8, sizeof(lvl_u8), false);
90 if (err < 0) {
91 LOG_ERR("IAS client level %d write failed: %d", lvl, err);
92 }
93
94 return err;
95 }
96
bt_ias_alert_lvl_disc_cb(struct bt_conn * conn,const struct bt_gatt_attr * attr,struct bt_gatt_discover_params * discover)97 static uint8_t bt_ias_alert_lvl_disc_cb(struct bt_conn *conn,
98 const struct bt_gatt_attr *attr,
99 struct bt_gatt_discover_params *discover)
100 {
101 const struct bt_gatt_chrc *chrc;
102
103 atomic_clear_bit(client_by_conn(conn)->flags, IAS_DISCOVER_IN_PROGRESS);
104
105 if (attr == NULL) {
106 discover_complete(conn, -ENOENT);
107
108 return BT_GATT_ITER_STOP;
109 }
110
111 chrc = (struct bt_gatt_chrc *)attr->user_data;
112
113 client_by_conn(conn)->alert_level_handle = chrc->value_handle;
114 discover_complete(conn, 0);
115
116 return BT_GATT_ITER_STOP;
117 }
118
bt_ias_prim_disc_cb(struct bt_conn * conn,const struct bt_gatt_attr * attr,struct bt_gatt_discover_params * discover)119 static uint8_t bt_ias_prim_disc_cb(struct bt_conn *conn,
120 const struct bt_gatt_attr *attr,
121 struct bt_gatt_discover_params *discover)
122 {
123 int err;
124 const struct bt_gatt_service_val *data;
125 struct bt_ias_client *client = client_by_conn(conn);
126
127 if (!attr) {
128 discover_complete(conn, -ENOENT);
129
130 return BT_GATT_ITER_STOP;
131 }
132
133 data = attr->user_data;
134
135 client->discover.uuid = alert_lvl_uuid;
136 client->discover.start_handle = attr->handle + 1;
137 client->discover.end_handle = data->end_handle;
138 client->discover.type = BT_GATT_DISCOVER_CHARACTERISTIC;
139 client->discover.func = bt_ias_alert_lvl_disc_cb;
140
141 err = bt_gatt_discover(conn, &client->discover);
142 if (err) {
143 discover_complete(conn, err);
144 }
145
146 return BT_GATT_ITER_STOP;
147 }
148
bt_ias_discover(struct bt_conn * conn)149 int bt_ias_discover(struct bt_conn *conn)
150 {
151 int err;
152 struct bt_ias_client *client = client_by_conn(conn);
153
154 CHECKIF(!conn || !ias_client_cb || !ias_client_cb->discover) {
155 return -EINVAL;
156 }
157
158 if (atomic_test_bit(client->flags, IAS_DISCOVER_IN_PROGRESS)) {
159 return -EBUSY;
160 }
161
162 client_cleanup(client);
163 atomic_set_bit(client->flags, IAS_DISCOVER_IN_PROGRESS);
164
165 client->discover.uuid = ias_uuid;
166 client->discover.func = bt_ias_prim_disc_cb;
167 client->discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
168 client->discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
169 client->discover.type = BT_GATT_DISCOVER_PRIMARY;
170
171 err = bt_gatt_discover(conn, &client->discover);
172 if (err < 0) {
173 discover_complete(conn, err);
174 }
175
176 return err;
177 }
178
bt_ias_client_cb_register(const struct bt_ias_client_cb * cb)179 int bt_ias_client_cb_register(const struct bt_ias_client_cb *cb)
180 {
181 CHECKIF(!cb) {
182 return -EINVAL;
183 }
184
185 CHECKIF(cb->discover == NULL) {
186 return -EINVAL;
187 }
188
189 CHECKIF(ias_client_cb) {
190 return -EALREADY;
191 }
192
193 ias_client_cb = cb;
194
195 return 0;
196 }
197