1 /* main.c - Bluetooth Cycling Speed and Cadence app main entry point */
2
3 /*
4 * Copyright (c) 2016 Intel Corporation
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 #include <stdbool.h>
10 #include <zephyr/types.h>
11 #include <stddef.h>
12 #include <string.h>
13 #include <errno.h>
14 #include <zephyr/random/random.h>
15 #include <zephyr/sys/printk.h>
16 #include <zephyr/sys/byteorder.h>
17 #include <zephyr/kernel.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/bas.h>
25
26 #define CSC_SUPPORTED_LOCATIONS { CSC_LOC_OTHER, \
27 CSC_LOC_FRONT_WHEEL, \
28 CSC_LOC_REAR_WHEEL, \
29 CSC_LOC_LEFT_CRANK, \
30 CSC_LOC_RIGHT_CRANK }
31 #define CSC_FEATURE (CSC_FEAT_WHEEL_REV | \
32 CSC_FEAT_CRANK_REV | \
33 CSC_FEAT_MULTI_SENSORS)
34
35 /* CSC Sensor Locations */
36 #define CSC_LOC_OTHER 0x00
37 #define CSC_LOC_TOP_OF_SHOE 0x01
38 #define CSC_LOC_IN_SHOE 0x02
39 #define CSC_LOC_HIP 0x03
40 #define CSC_LOC_FRONT_WHEEL 0x04
41 #define CSC_LOC_LEFT_CRANK 0x05
42 #define CSC_LOC_RIGHT_CRANK 0x06
43 #define CSC_LOC_LEFT_PEDAL 0x07
44 #define CSC_LOC_RIGHT_PEDAL 0x08
45 #define CSC_LOC_FRONT_HUB 0x09
46 #define CSC_LOC_REAR_DROPOUT 0x0a
47 #define CSC_LOC_CHAINSTAY 0x0b
48 #define CSC_LOC_REAR_WHEEL 0x0c
49 #define CSC_LOC_REAR_HUB 0x0d
50 #define CSC_LOC_CHEST 0x0e
51
52 /* CSC Application error codes */
53 #define CSC_ERR_IN_PROGRESS 0x80
54 #define CSC_ERR_CCC_CONFIG 0x81
55
56 /* SC Control Point Opcodes */
57 #define SC_CP_OP_SET_CWR 0x01
58 #define SC_CP_OP_CALIBRATION 0x02
59 #define SC_CP_OP_UPDATE_LOC 0x03
60 #define SC_CP_OP_REQ_SUPP_LOC 0x04
61 #define SC_CP_OP_RESPONSE 0x10
62
63 /* SC Control Point Response Values */
64 #define SC_CP_RSP_SUCCESS 0x01
65 #define SC_CP_RSP_OP_NOT_SUPP 0x02
66 #define SC_CP_RSP_INVAL_PARAM 0x03
67 #define SC_CP_RSP_FAILED 0x04
68
69 /* CSC Feature */
70 #define CSC_FEAT_WHEEL_REV BIT(0)
71 #define CSC_FEAT_CRANK_REV BIT(1)
72 #define CSC_FEAT_MULTI_SENSORS BIT(2)
73
74 /* CSC Measurement Flags */
75 #define CSC_WHEEL_REV_DATA_PRESENT BIT(0)
76 #define CSC_CRANK_REV_DATA_PRESENT BIT(1)
77
78 /* Cycling Speed and Cadence Service declaration */
79
80 static uint32_t c_wheel_revs; /* Cumulative Wheel Revolutions */
81 static uint8_t supported_locations[] = CSC_SUPPORTED_LOCATIONS;
82 static uint8_t sensor_location; /* Current Sensor Location */
83 static bool csc_simulate;
84 static bool ctrl_point_configured;
85
csc_meas_ccc_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)86 static void csc_meas_ccc_cfg_changed(const struct bt_gatt_attr *attr,
87 uint16_t value)
88 {
89 csc_simulate = value == BT_GATT_CCC_NOTIFY;
90 }
91
ctrl_point_ccc_cfg_changed(const struct bt_gatt_attr * attr,uint16_t value)92 static void ctrl_point_ccc_cfg_changed(const struct bt_gatt_attr *attr,
93 uint16_t value)
94 {
95 ctrl_point_configured = value == BT_GATT_CCC_INDICATE;
96 }
97
read_location(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)98 static ssize_t read_location(struct bt_conn *conn,
99 const struct bt_gatt_attr *attr, void *buf,
100 uint16_t len, uint16_t offset)
101 {
102 uint8_t *value = attr->user_data;
103
104 return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
105 sizeof(*value));
106 }
107
read_csc_feature(struct bt_conn * conn,const struct bt_gatt_attr * attr,void * buf,uint16_t len,uint16_t offset)108 static ssize_t read_csc_feature(struct bt_conn *conn,
109 const struct bt_gatt_attr *attr, void *buf,
110 uint16_t len, uint16_t offset)
111 {
112 uint16_t csc_feature = CSC_FEATURE;
113
114 return bt_gatt_attr_read(conn, attr, buf, len, offset,
115 &csc_feature, sizeof(csc_feature));
116 }
117
118 static void ctrl_point_ind(struct bt_conn *conn, uint8_t req_op, uint8_t status,
119 const void *data, uint16_t data_len);
120
121 struct write_sc_ctrl_point_req {
122 uint8_t op;
123 union {
124 uint32_t cwr;
125 uint8_t location;
126 };
127 } __packed;
128
write_ctrl_point(struct bt_conn * conn,const struct bt_gatt_attr * attr,const void * buf,uint16_t len,uint16_t offset,uint8_t flags)129 static ssize_t write_ctrl_point(struct bt_conn *conn,
130 const struct bt_gatt_attr *attr,
131 const void *buf, uint16_t len, uint16_t offset,
132 uint8_t flags)
133 {
134 const struct write_sc_ctrl_point_req *req = buf;
135 uint8_t status;
136 int i;
137
138 if (!ctrl_point_configured) {
139 return BT_GATT_ERR(CSC_ERR_CCC_CONFIG);
140 }
141
142 if (!len) {
143 return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
144 }
145
146 switch (req->op) {
147 case SC_CP_OP_SET_CWR:
148 if (len != sizeof(req->op) + sizeof(req->cwr)) {
149 status = SC_CP_RSP_INVAL_PARAM;
150 break;
151 }
152
153 c_wheel_revs = sys_le32_to_cpu(req->cwr);
154 status = SC_CP_RSP_SUCCESS;
155 break;
156 case SC_CP_OP_UPDATE_LOC:
157 if (len != sizeof(req->op) + sizeof(req->location)) {
158 status = SC_CP_RSP_INVAL_PARAM;
159 break;
160 }
161
162 /* Break if the requested location is the same as current one */
163 if (req->location == sensor_location) {
164 status = SC_CP_RSP_SUCCESS;
165 break;
166 }
167
168 /* Pre-set status */
169 status = SC_CP_RSP_INVAL_PARAM;
170
171 /* Check if requested location is supported */
172 for (i = 0; i < ARRAY_SIZE(supported_locations); i++) {
173 if (supported_locations[i] == req->location) {
174 sensor_location = req->location;
175 status = SC_CP_RSP_SUCCESS;
176 break;
177 }
178 }
179
180 break;
181 case SC_CP_OP_REQ_SUPP_LOC:
182 if (len != sizeof(req->op)) {
183 status = SC_CP_RSP_INVAL_PARAM;
184 break;
185 }
186
187 /* Indicate supported locations and return */
188 ctrl_point_ind(conn, req->op, SC_CP_RSP_SUCCESS,
189 &supported_locations,
190 sizeof(supported_locations));
191
192 return len;
193 default:
194 status = SC_CP_RSP_OP_NOT_SUPP;
195 }
196
197 ctrl_point_ind(conn, req->op, status, NULL, 0);
198
199 return len;
200 }
201
202 BT_GATT_SERVICE_DEFINE(csc_svc,
203 BT_GATT_PRIMARY_SERVICE(BT_UUID_CSC),
204 BT_GATT_CHARACTERISTIC(BT_UUID_CSC_MEASUREMENT, BT_GATT_CHRC_NOTIFY,
205 0x00, NULL, NULL, NULL),
206 BT_GATT_CCC(csc_meas_ccc_cfg_changed,
207 BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
208 BT_GATT_CHARACTERISTIC(BT_UUID_SENSOR_LOCATION, BT_GATT_CHRC_READ,
209 BT_GATT_PERM_READ, read_location, NULL,
210 &sensor_location),
211 BT_GATT_CHARACTERISTIC(BT_UUID_CSC_FEATURE, BT_GATT_CHRC_READ,
212 BT_GATT_PERM_READ, read_csc_feature, NULL, NULL),
213 BT_GATT_CHARACTERISTIC(BT_UUID_SC_CONTROL_POINT,
214 BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE,
215 BT_GATT_PERM_WRITE, NULL, write_ctrl_point,
216 &sensor_location),
217 BT_GATT_CCC(ctrl_point_ccc_cfg_changed,
218 BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
219 );
220
221 struct sc_ctrl_point_ind {
222 uint8_t op;
223 uint8_t req_op;
224 uint8_t status;
225 uint8_t data[];
226 } __packed;
227
ctrl_point_ind(struct bt_conn * conn,uint8_t req_op,uint8_t status,const void * data,uint16_t data_len)228 static void ctrl_point_ind(struct bt_conn *conn, uint8_t req_op, uint8_t status,
229 const void *data, uint16_t data_len)
230 {
231 struct sc_ctrl_point_ind *ind;
232 uint8_t buf[sizeof(*ind) + data_len];
233
234 ind = (void *) buf;
235 ind->op = SC_CP_OP_RESPONSE;
236 ind->req_op = req_op;
237 ind->status = status;
238
239 /* Send data (supported locations) if present */
240 if (data && data_len) {
241 memcpy(ind->data, data, data_len);
242 }
243
244 bt_gatt_notify(conn, &csc_svc.attrs[8], buf, sizeof(buf));
245 }
246
247 struct csc_measurement_nfy {
248 uint8_t flags;
249 uint8_t data[];
250 } __packed;
251
252 struct wheel_rev_data_nfy {
253 uint32_t cwr;
254 uint16_t lwet;
255 } __packed;
256
257 struct crank_rev_data_nfy {
258 uint16_t ccr;
259 uint16_t lcet;
260 } __packed;
261
measurement_nfy(struct bt_conn * conn,uint32_t cwr,uint16_t lwet,uint16_t ccr,uint16_t lcet)262 static void measurement_nfy(struct bt_conn *conn, uint32_t cwr, uint16_t lwet,
263 uint16_t ccr, uint16_t lcet)
264 {
265 struct csc_measurement_nfy *nfy;
266 uint8_t buf[sizeof(*nfy) +
267 (cwr ? sizeof(struct wheel_rev_data_nfy) : 0) +
268 (ccr ? sizeof(struct crank_rev_data_nfy) : 0)];
269 uint16_t len = 0U;
270
271 nfy = (void *) buf;
272 nfy->flags = 0U;
273
274 /* Send Wheel Revolution data is present */
275 if (cwr) {
276 struct wheel_rev_data_nfy data;
277
278 nfy->flags |= CSC_WHEEL_REV_DATA_PRESENT;
279 data.cwr = sys_cpu_to_le32(cwr);
280 data.lwet = sys_cpu_to_le16(lwet);
281
282 memcpy(nfy->data, &data, sizeof(data));
283 len += sizeof(data);
284 }
285
286 /* Send Crank Revolution data is present */
287 if (ccr) {
288 struct crank_rev_data_nfy data;
289
290 nfy->flags |= CSC_CRANK_REV_DATA_PRESENT;
291 data.ccr = sys_cpu_to_le16(ccr);
292 data.lcet = sys_cpu_to_le16(lcet);
293
294 memcpy(nfy->data + len, &data, sizeof(data));
295 }
296
297 bt_gatt_notify(NULL, &csc_svc.attrs[1], buf, sizeof(buf));
298 }
299
300 static uint16_t lwet; /* Last Wheel Event Time */
301 static uint16_t ccr; /* Cumulative Crank Revolutions */
302 static uint16_t lcet; /* Last Crank Event Time */
303
csc_simulation(void)304 static void csc_simulation(void)
305 {
306 static uint8_t i;
307 uint8_t rnd = sys_rand8_get();
308 bool nfy_crank = false, nfy_wheel = false;
309
310 /* Measurements don't have to be updated every second */
311 if (!(i % 2)) {
312 lwet += 1050 + rnd % 50;
313 c_wheel_revs += 2U;
314 nfy_wheel = true;
315 }
316
317 if (!(i % 3)) {
318 lcet += 1000 + rnd % 50;
319 ccr += 1U;
320 nfy_crank = true;
321 }
322
323 /*
324 * In typical applications, the CSC Measurement characteristic is
325 * notified approximately once per second. This interval may vary
326 * and is determined by the Server and not required to be configurable
327 * by the Client.
328 */
329 measurement_nfy(NULL, nfy_wheel ? c_wheel_revs : 0, nfy_wheel ? lwet : 0,
330 nfy_crank ? ccr : 0, nfy_crank ? lcet : 0);
331
332 /*
333 * The Last Crank Event Time value and Last Wheel Event Time roll over
334 * every 64 seconds.
335 */
336 if (!(i % 64)) {
337 lcet = 0U;
338 lwet = 0U;
339 i = 0U;
340 }
341
342 i++;
343 }
344
connected(struct bt_conn * conn,uint8_t err)345 static void connected(struct bt_conn *conn, uint8_t err)
346 {
347 if (err) {
348 printk("Connection failed, err 0x%02x %s\n", err, bt_hci_err_to_str(err));
349 } else {
350 printk("Connected\n");
351 }
352 }
353
disconnected(struct bt_conn * conn,uint8_t reason)354 static void disconnected(struct bt_conn *conn, uint8_t reason)
355 {
356 printk("Disconnected, reason 0x%02x %s\n", reason, bt_hci_err_to_str(reason));
357 }
358
359 BT_CONN_CB_DEFINE(conn_callbacks) = {
360 .connected = connected,
361 .disconnected = disconnected,
362 };
363
364 static const struct bt_data ad[] = {
365 BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
366 BT_DATA_BYTES(BT_DATA_UUID16_ALL,
367 BT_UUID_16_ENCODE(BT_UUID_CSC_VAL),
368 BT_UUID_16_ENCODE(BT_UUID_BAS_VAL))
369 };
370
371 static const struct bt_data sd[] = {
372 BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
373 };
374
bt_ready(void)375 static void bt_ready(void)
376 {
377 int err;
378
379 printk("Bluetooth initialized\n");
380
381 err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
382 if (err) {
383 printk("Advertising failed to start (err %d)\n", err);
384 return;
385 }
386
387 printk("Advertising successfully started\n");
388 }
389
bas_notify(void)390 static void bas_notify(void)
391 {
392 uint8_t battery_level = bt_bas_get_battery_level();
393
394 battery_level--;
395
396 if (!battery_level) {
397 battery_level = 100U;
398 }
399
400 bt_bas_set_battery_level(battery_level);
401 }
402
main(void)403 int main(void)
404 {
405 int err;
406
407 err = bt_enable(NULL);
408 if (err) {
409 printk("Bluetooth init failed (err %d)\n", err);
410 return 0;
411 }
412
413 bt_ready();
414
415 while (1) {
416 k_sleep(K_SECONDS(1));
417
418 /* CSC simulation */
419 if (csc_simulate) {
420 csc_simulation();
421 }
422
423 /* Battery level simulation */
424 bas_notify();
425 }
426 return 0;
427 }
428