| /* |
| * Copyright (c) 2023 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/bluetooth/att.h> |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/gatt.h> |
| |
| #define NUM_RSP_SLOTS 5 |
| #define NUM_SUBEVENTS 5 |
| #define PACKET_SIZE 5 |
| #define NAME_LEN 30 |
| |
| static K_SEM_DEFINE(sem_connected, 0, 1); |
| static K_SEM_DEFINE(sem_discovered, 0, 1); |
| static K_SEM_DEFINE(sem_written, 0, 1); |
| static K_SEM_DEFINE(sem_disconnected, 0, 1); |
| |
| static struct bt_uuid_128 pawr_char_uuid = |
| BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1)); |
| static uint16_t pawr_attr_handle; |
| static const struct bt_le_per_adv_param per_adv_params = { |
| .interval_min = 0xFF, |
| .interval_max = 0xFF, |
| .options = 0, |
| .num_subevents = NUM_SUBEVENTS, |
| .subevent_interval = 0x30, |
| .response_slot_delay = 0x5, |
| .response_slot_spacing = 0x50, |
| .num_response_slots = NUM_RSP_SLOTS, |
| }; |
| |
| static struct bt_le_per_adv_subevent_data_params subevent_data_params[NUM_SUBEVENTS]; |
| static struct net_buf_simple bufs[NUM_SUBEVENTS]; |
| static uint8_t backing_store[NUM_SUBEVENTS][PACKET_SIZE]; |
| |
| BUILD_ASSERT(ARRAY_SIZE(bufs) == ARRAY_SIZE(subevent_data_params)); |
| BUILD_ASSERT(ARRAY_SIZE(backing_store) == ARRAY_SIZE(subevent_data_params)); |
| |
| static uint8_t counter; |
| |
| static void request_cb(struct bt_le_ext_adv *adv, const struct bt_le_per_adv_data_request *request) |
| { |
| int err; |
| uint8_t to_send; |
| struct net_buf_simple *buf; |
| |
| to_send = MIN(request->count, ARRAY_SIZE(subevent_data_params)); |
| |
| for (size_t i = 0; i < to_send; i++) { |
| buf = &bufs[i]; |
| buf->data[buf->len - 1] = counter++; |
| |
| subevent_data_params[i].subevent = |
| (request->start + i) % per_adv_params.num_subevents; |
| subevent_data_params[i].response_slot_start = 0; |
| subevent_data_params[i].response_slot_count = NUM_RSP_SLOTS; |
| subevent_data_params[i].data = buf; |
| } |
| |
| err = bt_le_per_adv_set_subevent_data(adv, to_send, subevent_data_params); |
| if (err) { |
| printk("Failed to set subevent data (err %d)\n", err); |
| } else { |
| printk("Subevent data set %d\n", counter); |
| } |
| } |
| |
| static bool print_ad_field(struct bt_data *data, void *user_data) |
| { |
| ARG_UNUSED(user_data); |
| |
| printk(" 0x%02X: ", data->type); |
| for (size_t i = 0; i < data->data_len; i++) { |
| printk("%02X", data->data[i]); |
| } |
| |
| printk("\n"); |
| |
| return true; |
| } |
| |
| static struct bt_conn *default_conn; |
| |
| static void response_cb(struct bt_le_ext_adv *adv, struct bt_le_per_adv_response_info *info, |
| struct net_buf_simple *buf) |
| { |
| if (buf) { |
| printk("Response: subevent %d, slot %d\n", info->subevent, info->response_slot); |
| bt_data_parse(buf, print_ad_field, NULL); |
| } else { |
| printk("Failed to receive response: subevent %d, slot %d\n", info->subevent, |
| info->response_slot); |
| } |
| } |
| |
| static const struct bt_le_ext_adv_cb adv_cb = { |
| .pawr_data_request = request_cb, |
| .pawr_response = response_cb, |
| }; |
| |
| void connected_cb(struct bt_conn *conn, uint8_t err) |
| { |
| printk("Connected (err 0x%02X)\n", err); |
| |
| __ASSERT(conn == default_conn, "Unexpected connected callback"); |
| |
| if (err) { |
| bt_conn_unref(default_conn); |
| default_conn = NULL; |
| } |
| } |
| |
| void disconnected_cb(struct bt_conn *conn, uint8_t reason) |
| { |
| printk("Disconnected (reason 0x%02X)\n", reason); |
| |
| bt_conn_unref(default_conn); |
| default_conn = NULL; |
| |
| k_sem_give(&sem_disconnected); |
| } |
| |
| void remote_info_available_cb(struct bt_conn *conn, struct bt_conn_remote_info *remote_info) |
| { |
| /* Need to wait for remote info before initiating PAST */ |
| k_sem_give(&sem_connected); |
| } |
| |
| BT_CONN_CB_DEFINE(conn_cb) = { |
| .connected = connected_cb, |
| .disconnected = disconnected_cb, |
| .remote_info_available = remote_info_available_cb, |
| }; |
| |
| static bool data_cb(struct bt_data *data, void *user_data) |
| { |
| char *name = user_data; |
| uint8_t len; |
| |
| switch (data->type) { |
| case BT_DATA_NAME_SHORTENED: |
| case BT_DATA_NAME_COMPLETE: |
| len = MIN(data->data_len, NAME_LEN - 1); |
| memcpy(name, data->data, len); |
| name[len] = '\0'; |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, |
| struct net_buf_simple *ad) |
| { |
| char addr_str[BT_ADDR_LE_STR_LEN]; |
| char name[NAME_LEN]; |
| int err; |
| |
| if (default_conn) { |
| return; |
| } |
| |
| /* We're only interested in connectable events */ |
| if (type != BT_GAP_ADV_TYPE_ADV_IND && type != BT_GAP_ADV_TYPE_ADV_DIRECT_IND) { |
| return; |
| } |
| |
| (void)memset(name, 0, sizeof(name)); |
| bt_data_parse(ad, data_cb, name); |
| |
| if (strcmp(name, "PAwR sync sample")) { |
| return; |
| } |
| |
| if (bt_le_scan_stop()) { |
| return; |
| } |
| |
| err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, |
| &default_conn); |
| if (err) { |
| printk("Create conn to %s failed (%u)\n", addr_str, err); |
| } |
| } |
| |
| static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| struct bt_gatt_discover_params *params) |
| { |
| struct bt_gatt_chrc *chrc; |
| char str[BT_UUID_STR_LEN]; |
| |
| printk("Discovery: attr %p\n", attr); |
| |
| if (!attr) { |
| return BT_GATT_ITER_STOP; |
| } |
| |
| chrc = (struct bt_gatt_chrc *)attr->user_data; |
| |
| bt_uuid_to_str(chrc->uuid, str, sizeof(str)); |
| printk("UUID %s\n", str); |
| |
| if (!bt_uuid_cmp(chrc->uuid, &pawr_char_uuid.uuid)) { |
| pawr_attr_handle = chrc->value_handle; |
| |
| printk("Characteristic handle: %d\n", pawr_attr_handle); |
| |
| k_sem_give(&sem_discovered); |
| } |
| |
| return BT_GATT_ITER_STOP; |
| } |
| |
| static void write_func(struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params) |
| { |
| if (err) { |
| printk("Write failed (err %d)\n", err); |
| |
| return; |
| } |
| |
| k_sem_give(&sem_written); |
| } |
| |
| void init_bufs(void) |
| { |
| for (size_t i = 0; i < ARRAY_SIZE(backing_store); i++) { |
| backing_store[i][0] = ARRAY_SIZE(backing_store[i]) - 1; |
| backing_store[i][1] = BT_DATA_MANUFACTURER_DATA; |
| backing_store[i][2] = 0x59; /* Nordic */ |
| backing_store[i][3] = 0x00; |
| |
| net_buf_simple_init_with_data(&bufs[i], &backing_store[i], |
| ARRAY_SIZE(backing_store[i])); |
| } |
| } |
| |
| #define MAX_SYNCS (NUM_SUBEVENTS * NUM_RSP_SLOTS) |
| struct pawr_timing { |
| uint8_t subevent; |
| uint8_t response_slot; |
| } __packed; |
| |
| static uint8_t num_synced; |
| |
| int main(void) |
| { |
| int err; |
| struct bt_le_ext_adv *pawr_adv; |
| struct bt_gatt_discover_params discover_params; |
| struct bt_gatt_write_params write_params; |
| struct pawr_timing sync_config; |
| |
| init_bufs(); |
| |
| printk("Starting Periodic Advertising Demo\n"); |
| |
| /* Initialize the Bluetooth Subsystem */ |
| err = bt_enable(NULL); |
| if (err) { |
| printk("Bluetooth init failed (err %d)\n", err); |
| return 0; |
| } |
| |
| /* Create a non-connectable non-scannable advertising set */ |
| err = bt_le_ext_adv_create(BT_LE_EXT_ADV_NCONN, &adv_cb, &pawr_adv); |
| if (err) { |
| printk("Failed to create advertising set (err %d)\n", err); |
| return 0; |
| } |
| |
| /* Set periodic advertising parameters */ |
| err = bt_le_per_adv_set_param(pawr_adv, &per_adv_params); |
| if (err) { |
| printk("Failed to set periodic advertising parameters (err %d)\n", err); |
| return 0; |
| } |
| |
| /* Enable Periodic Advertising */ |
| err = bt_le_per_adv_start(pawr_adv); |
| if (err) { |
| printk("Failed to enable periodic advertising (err %d)\n", err); |
| return 0; |
| } |
| |
| printk("Start Periodic Advertising\n"); |
| err = bt_le_ext_adv_start(pawr_adv, BT_LE_EXT_ADV_START_DEFAULT); |
| if (err) { |
| printk("Failed to start extended advertising (err %d)\n", err); |
| return 0; |
| } |
| |
| while (num_synced < MAX_SYNCS) { |
| err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found); |
| if (err) { |
| printk("Scanning failed to start (err %d)\n", err); |
| return 0; |
| } |
| |
| printk("Scanning successfully started\n"); |
| |
| k_sem_take(&sem_connected, K_FOREVER); |
| |
| err = bt_le_per_adv_set_info_transfer(pawr_adv, default_conn, 0); |
| if (err) { |
| printk("Failed to send PAST (err %d)\n", err); |
| |
| goto disconnect; |
| } |
| |
| printk("PAST sent\n"); |
| |
| discover_params.uuid = &pawr_char_uuid.uuid; |
| discover_params.func = discover_func; |
| discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; |
| discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; |
| discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; |
| err = bt_gatt_discover(default_conn, &discover_params); |
| if (err) { |
| printk("Discovery failed (err %d)\n", err); |
| |
| goto disconnect; |
| } |
| |
| printk("Discovery started\n"); |
| |
| err = k_sem_take(&sem_discovered, K_SECONDS(10)); |
| if (err) { |
| printk("Timed out during GATT discovery\n"); |
| |
| goto disconnect; |
| } |
| |
| sync_config.subevent = num_synced % NUM_SUBEVENTS; |
| sync_config.response_slot = num_synced / NUM_RSP_SLOTS; |
| num_synced++; |
| |
| write_params.func = write_func; |
| write_params.handle = pawr_attr_handle; |
| write_params.offset = 0; |
| write_params.data = &sync_config; |
| write_params.length = sizeof(sync_config); |
| |
| err = bt_gatt_write(default_conn, &write_params); |
| if (err) { |
| printk("Write failed (err %d)\n", err); |
| num_synced--; |
| |
| goto disconnect; |
| } |
| |
| printk("Write started\n"); |
| |
| err = k_sem_take(&sem_written, K_SECONDS(10)); |
| if (err) { |
| printk("Timed out during GATT write\n"); |
| num_synced--; |
| |
| goto disconnect; |
| } |
| |
| printk("PAwR config written to sync %d, disconnecting\n", num_synced - 1); |
| |
| disconnect: |
| err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); |
| if (err) { |
| return 0; |
| } |
| |
| k_sem_take(&sem_disconnected, K_FOREVER); |
| } |
| |
| printk("Maximum numnber of syncs onboarded\n"); |
| |
| while (true) { |
| k_sleep(K_SECONDS(1)); |
| } |
| |
| return 0; |
| } |