| /* main.c - Bluetooth Cycling Speed and Cadence app main entry point */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdbool.h> |
| #include <zephyr/types.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <zephyr/random/random.h> |
| #include <zephyr/sys/printk.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/hci.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/uuid.h> |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/services/bas.h> |
| |
| #define CSC_SUPPORTED_LOCATIONS { CSC_LOC_OTHER, \ |
| CSC_LOC_FRONT_WHEEL, \ |
| CSC_LOC_REAR_WHEEL, \ |
| CSC_LOC_LEFT_CRANK, \ |
| CSC_LOC_RIGHT_CRANK } |
| #define CSC_FEATURE (CSC_FEAT_WHEEL_REV | \ |
| CSC_FEAT_CRANK_REV | \ |
| CSC_FEAT_MULTI_SENSORS) |
| |
| /* CSC Sensor Locations */ |
| #define CSC_LOC_OTHER 0x00 |
| #define CSC_LOC_TOP_OF_SHOE 0x01 |
| #define CSC_LOC_IN_SHOE 0x02 |
| #define CSC_LOC_HIP 0x03 |
| #define CSC_LOC_FRONT_WHEEL 0x04 |
| #define CSC_LOC_LEFT_CRANK 0x05 |
| #define CSC_LOC_RIGHT_CRANK 0x06 |
| #define CSC_LOC_LEFT_PEDAL 0x07 |
| #define CSC_LOC_RIGHT_PEDAL 0x08 |
| #define CSC_LOC_FRONT_HUB 0x09 |
| #define CSC_LOC_REAR_DROPOUT 0x0a |
| #define CSC_LOC_CHAINSTAY 0x0b |
| #define CSC_LOC_REAR_WHEEL 0x0c |
| #define CSC_LOC_REAR_HUB 0x0d |
| #define CSC_LOC_CHEST 0x0e |
| |
| /* CSC Application error codes */ |
| #define CSC_ERR_IN_PROGRESS 0x80 |
| #define CSC_ERR_CCC_CONFIG 0x81 |
| |
| /* SC Control Point Opcodes */ |
| #define SC_CP_OP_SET_CWR 0x01 |
| #define SC_CP_OP_CALIBRATION 0x02 |
| #define SC_CP_OP_UPDATE_LOC 0x03 |
| #define SC_CP_OP_REQ_SUPP_LOC 0x04 |
| #define SC_CP_OP_RESPONSE 0x10 |
| |
| /* SC Control Point Response Values */ |
| #define SC_CP_RSP_SUCCESS 0x01 |
| #define SC_CP_RSP_OP_NOT_SUPP 0x02 |
| #define SC_CP_RSP_INVAL_PARAM 0x03 |
| #define SC_CP_RSP_FAILED 0x04 |
| |
| /* CSC Feature */ |
| #define CSC_FEAT_WHEEL_REV BIT(0) |
| #define CSC_FEAT_CRANK_REV BIT(1) |
| #define CSC_FEAT_MULTI_SENSORS BIT(2) |
| |
| /* CSC Measurement Flags */ |
| #define CSC_WHEEL_REV_DATA_PRESENT BIT(0) |
| #define CSC_CRANK_REV_DATA_PRESENT BIT(1) |
| |
| /* Cycling Speed and Cadence Service declaration */ |
| |
| static uint32_t c_wheel_revs; /* Cumulative Wheel Revolutions */ |
| static uint8_t supported_locations[] = CSC_SUPPORTED_LOCATIONS; |
| static uint8_t sensor_location; /* Current Sensor Location */ |
| static bool csc_simulate; |
| static bool ctrl_point_configured; |
| |
| static void csc_meas_ccc_cfg_changed(const struct bt_gatt_attr *attr, |
| uint16_t value) |
| { |
| csc_simulate = value == BT_GATT_CCC_NOTIFY; |
| } |
| |
| static void ctrl_point_ccc_cfg_changed(const struct bt_gatt_attr *attr, |
| uint16_t value) |
| { |
| ctrl_point_configured = value == BT_GATT_CCC_INDICATE; |
| } |
| |
| static ssize_t read_location(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, void *buf, |
| uint16_t len, uint16_t offset) |
| { |
| uint8_t *value = attr->user_data; |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, value, |
| sizeof(*value)); |
| } |
| |
| static ssize_t read_csc_feature(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, void *buf, |
| uint16_t len, uint16_t offset) |
| { |
| uint16_t csc_feature = CSC_FEATURE; |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, |
| &csc_feature, sizeof(csc_feature)); |
| } |
| |
| static void ctrl_point_ind(struct bt_conn *conn, uint8_t req_op, uint8_t status, |
| const void *data, uint16_t data_len); |
| |
| struct write_sc_ctrl_point_req { |
| uint8_t op; |
| union { |
| uint32_t cwr; |
| uint8_t location; |
| }; |
| } __packed; |
| |
| static ssize_t 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) |
| { |
| const struct write_sc_ctrl_point_req *req = buf; |
| uint8_t status; |
| int i; |
| |
| if (!ctrl_point_configured) { |
| return BT_GATT_ERR(CSC_ERR_CCC_CONFIG); |
| } |
| |
| if (!len) { |
| return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); |
| } |
| |
| switch (req->op) { |
| case SC_CP_OP_SET_CWR: |
| if (len != sizeof(req->op) + sizeof(req->cwr)) { |
| status = SC_CP_RSP_INVAL_PARAM; |
| break; |
| } |
| |
| c_wheel_revs = sys_le32_to_cpu(req->cwr); |
| status = SC_CP_RSP_SUCCESS; |
| break; |
| case SC_CP_OP_UPDATE_LOC: |
| if (len != sizeof(req->op) + sizeof(req->location)) { |
| status = SC_CP_RSP_INVAL_PARAM; |
| break; |
| } |
| |
| /* Break if the requested location is the same as current one */ |
| if (req->location == sensor_location) { |
| status = SC_CP_RSP_SUCCESS; |
| break; |
| } |
| |
| /* Pre-set status */ |
| status = SC_CP_RSP_INVAL_PARAM; |
| |
| /* Check if requested location is supported */ |
| for (i = 0; i < ARRAY_SIZE(supported_locations); i++) { |
| if (supported_locations[i] == req->location) { |
| sensor_location = req->location; |
| status = SC_CP_RSP_SUCCESS; |
| break; |
| } |
| } |
| |
| break; |
| case SC_CP_OP_REQ_SUPP_LOC: |
| if (len != sizeof(req->op)) { |
| status = SC_CP_RSP_INVAL_PARAM; |
| break; |
| } |
| |
| /* Indicate supported locations and return */ |
| ctrl_point_ind(conn, req->op, SC_CP_RSP_SUCCESS, |
| &supported_locations, |
| sizeof(supported_locations)); |
| |
| return len; |
| default: |
| status = SC_CP_RSP_OP_NOT_SUPP; |
| } |
| |
| ctrl_point_ind(conn, req->op, status, NULL, 0); |
| |
| return len; |
| } |
| |
| BT_GATT_SERVICE_DEFINE(csc_svc, |
| BT_GATT_PRIMARY_SERVICE(BT_UUID_CSC), |
| BT_GATT_CHARACTERISTIC(BT_UUID_CSC_MEASUREMENT, BT_GATT_CHRC_NOTIFY, |
| 0x00, NULL, NULL, NULL), |
| BT_GATT_CCC(csc_meas_ccc_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), |
| BT_GATT_CHARACTERISTIC(BT_UUID_SENSOR_LOCATION, BT_GATT_CHRC_READ, |
| BT_GATT_PERM_READ, read_location, NULL, |
| &sensor_location), |
| BT_GATT_CHARACTERISTIC(BT_UUID_CSC_FEATURE, BT_GATT_CHRC_READ, |
| BT_GATT_PERM_READ, read_csc_feature, NULL, NULL), |
| BT_GATT_CHARACTERISTIC(BT_UUID_SC_CONTROL_POINT, |
| BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, |
| BT_GATT_PERM_WRITE, NULL, write_ctrl_point, |
| &sensor_location), |
| BT_GATT_CCC(ctrl_point_ccc_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), |
| ); |
| |
| struct sc_ctrl_point_ind { |
| uint8_t op; |
| uint8_t req_op; |
| uint8_t status; |
| uint8_t data[]; |
| } __packed; |
| |
| static void ctrl_point_ind(struct bt_conn *conn, uint8_t req_op, uint8_t status, |
| const void *data, uint16_t data_len) |
| { |
| struct sc_ctrl_point_ind *ind; |
| uint8_t buf[sizeof(*ind) + data_len]; |
| |
| ind = (void *) buf; |
| ind->op = SC_CP_OP_RESPONSE; |
| ind->req_op = req_op; |
| ind->status = status; |
| |
| /* Send data (supported locations) if present */ |
| if (data && data_len) { |
| memcpy(ind->data, data, data_len); |
| } |
| |
| bt_gatt_notify(conn, &csc_svc.attrs[8], buf, sizeof(buf)); |
| } |
| |
| struct csc_measurement_nfy { |
| uint8_t flags; |
| uint8_t data[]; |
| } __packed; |
| |
| struct wheel_rev_data_nfy { |
| uint32_t cwr; |
| uint16_t lwet; |
| } __packed; |
| |
| struct crank_rev_data_nfy { |
| uint16_t ccr; |
| uint16_t lcet; |
| } __packed; |
| |
| static void measurement_nfy(struct bt_conn *conn, uint32_t cwr, uint16_t lwet, |
| uint16_t ccr, uint16_t lcet) |
| { |
| struct csc_measurement_nfy *nfy; |
| uint8_t buf[sizeof(*nfy) + |
| (cwr ? sizeof(struct wheel_rev_data_nfy) : 0) + |
| (ccr ? sizeof(struct crank_rev_data_nfy) : 0)]; |
| uint16_t len = 0U; |
| |
| nfy = (void *) buf; |
| nfy->flags = 0U; |
| |
| /* Send Wheel Revolution data is present */ |
| if (cwr) { |
| struct wheel_rev_data_nfy data; |
| |
| nfy->flags |= CSC_WHEEL_REV_DATA_PRESENT; |
| data.cwr = sys_cpu_to_le32(cwr); |
| data.lwet = sys_cpu_to_le16(lwet); |
| |
| memcpy(nfy->data, &data, sizeof(data)); |
| len += sizeof(data); |
| } |
| |
| /* Send Crank Revolution data is present */ |
| if (ccr) { |
| struct crank_rev_data_nfy data; |
| |
| nfy->flags |= CSC_CRANK_REV_DATA_PRESENT; |
| data.ccr = sys_cpu_to_le16(ccr); |
| data.lcet = sys_cpu_to_le16(lcet); |
| |
| memcpy(nfy->data + len, &data, sizeof(data)); |
| } |
| |
| bt_gatt_notify(NULL, &csc_svc.attrs[1], buf, sizeof(buf)); |
| } |
| |
| static uint16_t lwet; /* Last Wheel Event Time */ |
| static uint16_t ccr; /* Cumulative Crank Revolutions */ |
| static uint16_t lcet; /* Last Crank Event Time */ |
| |
| static void csc_simulation(void) |
| { |
| static uint8_t i; |
| uint32_t rand = sys_rand32_get(); |
| bool nfy_crank = false, nfy_wheel = false; |
| |
| /* Measurements don't have to be updated every second */ |
| if (!(i % 2)) { |
| lwet += 1050 + rand % 50; |
| c_wheel_revs += 2U; |
| nfy_wheel = true; |
| } |
| |
| if (!(i % 3)) { |
| lcet += 1000 + rand % 50; |
| ccr += 1U; |
| nfy_crank = true; |
| } |
| |
| /* |
| * In typical applications, the CSC Measurement characteristic is |
| * notified approximately once per second. This interval may vary |
| * and is determined by the Server and not required to be configurable |
| * by the Client. |
| */ |
| measurement_nfy(NULL, nfy_wheel ? c_wheel_revs : 0, nfy_wheel ? lwet : 0, |
| nfy_crank ? ccr : 0, nfy_crank ? lcet : 0); |
| |
| /* |
| * The Last Crank Event Time value and Last Wheel Event Time roll over |
| * every 64 seconds. |
| */ |
| if (!(i % 64)) { |
| lcet = 0U; |
| lwet = 0U; |
| i = 0U; |
| } |
| |
| i++; |
| } |
| |
| static void connected(struct bt_conn *conn, uint8_t err) |
| { |
| if (err) { |
| printk("Connection failed (err 0x%02x)\n", err); |
| } else { |
| printk("Connected\n"); |
| } |
| } |
| |
| static void disconnected(struct bt_conn *conn, uint8_t reason) |
| { |
| printk("Disconnected (reason 0x%02x)\n", reason); |
| } |
| |
| BT_CONN_CB_DEFINE(conn_callbacks) = { |
| .connected = connected, |
| .disconnected = disconnected, |
| }; |
| |
| static const struct bt_data ad[] = { |
| BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), |
| BT_DATA_BYTES(BT_DATA_UUID16_ALL, |
| BT_UUID_16_ENCODE(BT_UUID_CSC_VAL), |
| BT_UUID_16_ENCODE(BT_UUID_BAS_VAL)) |
| }; |
| |
| static void bt_ready(void) |
| { |
| int err; |
| |
| printk("Bluetooth initialized\n"); |
| |
| err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0); |
| if (err) { |
| printk("Advertising failed to start (err %d)\n", err); |
| return; |
| } |
| |
| printk("Advertising successfully started\n"); |
| } |
| |
| static void bas_notify(void) |
| { |
| uint8_t battery_level = bt_bas_get_battery_level(); |
| |
| battery_level--; |
| |
| if (!battery_level) { |
| battery_level = 100U; |
| } |
| |
| bt_bas_set_battery_level(battery_level); |
| } |
| |
| int main(void) |
| { |
| int err; |
| |
| err = bt_enable(NULL); |
| if (err) { |
| printk("Bluetooth init failed (err %d)\n", err); |
| return 0; |
| } |
| |
| bt_ready(); |
| |
| while (1) { |
| k_sleep(K_SECONDS(1)); |
| |
| /* CSC simulation */ |
| if (csc_simulate) { |
| csc_simulation(); |
| } |
| |
| /* Battery level simulation */ |
| bas_notify(); |
| } |
| return 0; |
| } |