| /* |
| * Copyright 2023 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/types.h> |
| #include <stddef.h> |
| #include <errno.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/printk.h> |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/audio/audio.h> |
| #include <zephyr/bluetooth/audio/bap.h> |
| #include <zephyr/bluetooth/audio/bap_lc3_preset.h> |
| #include <zephyr/bluetooth/audio/tmap.h> |
| #include <zephyr/bluetooth/audio/cap.h> |
| |
| #include "tmap_central.h" |
| |
| static struct bt_conn *default_conn; |
| |
| static K_SEM_DEFINE(sem_connected, 0, 1); |
| static K_SEM_DEFINE(sem_security_updated, 0, 1); |
| static K_SEM_DEFINE(sem_disconnected, 0, 1); |
| static K_SEM_DEFINE(sem_mtu_exchanged, 0, 1); |
| static K_SEM_DEFINE(sem_discovery_done, 0, 1); |
| |
| static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx) |
| { |
| printk("MTU exchanged: %u/%u\n", tx, rx); |
| k_sem_give(&sem_mtu_exchanged); |
| } |
| |
| static struct bt_gatt_cb gatt_callbacks = { |
| .att_mtu_updated = att_mtu_updated |
| }; |
| |
| void tmap_discovery_complete(enum bt_tmap_role role, struct bt_conn *conn, int err) |
| { |
| if (conn != default_conn) { |
| return; |
| } |
| |
| if (err) { |
| printk("TMAS discovery failed! (err %d)\n", err); |
| return; |
| } |
| printk("TMAS discovery done\n"); |
| k_sem_give(&sem_discovery_done); |
| } |
| |
| static struct bt_tmap_cb tmap_callbacks = { |
| .discovery_complete = tmap_discovery_complete |
| }; |
| |
| static void start_scan(void); |
| |
| static int init(void) |
| { |
| int err; |
| |
| err = bt_enable(NULL); |
| if (err != 0) { |
| printk("Bluetooth enable failed (err %d)\n", err); |
| return err; |
| } |
| |
| printk("Bluetooth initialized\n"); |
| bt_gatt_cb_register(&gatt_callbacks); |
| |
| return 0; |
| } |
| |
| static void connected(struct bt_conn *conn, uint8_t err) |
| { |
| char addr[BT_ADDR_LE_STR_LEN]; |
| |
| (void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); |
| |
| if (err != 0) { |
| printk("Failed to connect to %s (%u)\n", addr, err); |
| |
| bt_conn_unref(default_conn); |
| default_conn = NULL; |
| |
| start_scan(); |
| return; |
| } |
| |
| if (conn != default_conn) { |
| return; |
| } |
| |
| printk("Connected: %s\n", addr); |
| k_sem_give(&sem_connected); |
| } |
| |
| static void disconnected(struct bt_conn *conn, uint8_t reason) |
| { |
| char addr[BT_ADDR_LE_STR_LEN]; |
| |
| if (conn != default_conn) { |
| return; |
| } |
| |
| (void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); |
| |
| printk("Disconnected: %s (reason 0x%02x)\n", addr, reason); |
| |
| bt_conn_unref(default_conn); |
| default_conn = NULL; |
| |
| k_sem_give(&sem_disconnected); |
| } |
| |
| static void security_changed(struct bt_conn *conn, bt_security_t level, |
| enum bt_security_err err) |
| { |
| if (err == 0) { |
| printk("Security changed: %u\n", err); |
| k_sem_give(&sem_security_updated); |
| } else { |
| printk("Failed to set security level: %u\n", err); |
| } |
| } |
| |
| BT_CONN_CB_DEFINE(conn_callbacks) = { |
| .connected = connected, |
| .disconnected = disconnected, |
| .security_changed = security_changed |
| }; |
| |
| static bool check_audio_support_and_connect(struct bt_data *data, void *user_data) |
| { |
| bt_addr_le_t *addr = user_data; |
| struct net_buf_simple tmas_svc_data; |
| struct bt_uuid *uuid; |
| uint16_t uuid_val; |
| uint16_t peer_tmap_role = 0; |
| int err; |
| |
| printk("[AD]: %u data_len %u\n", data->type, data->data_len); |
| |
| if (data->type != BT_DATA_SVC_DATA16) { |
| return true; /* Continue parsing to next AD data type */ |
| } |
| |
| if (data->data_len < sizeof(uuid_val)) { |
| printk("AD invalid size %u\n", data->data_len); |
| return true; /* Continue parsing to next AD data type */ |
| } |
| |
| net_buf_simple_init_with_data(&tmas_svc_data, (void *)data->data, data->data_len); |
| uuid_val = net_buf_simple_pull_le16(&tmas_svc_data); |
| uuid = BT_UUID_DECLARE_16(sys_le16_to_cpu(uuid_val)); |
| if (bt_uuid_cmp(uuid, BT_UUID_TMAS) != 0) { |
| /* We are looking for the TMAS service data */ |
| return true; /* Continue parsing to next AD data type */ |
| } |
| |
| printk("Found TMAS in peer adv data!\n"); |
| if (tmas_svc_data.len < sizeof(peer_tmap_role)) { |
| printk("AD invalid size %u\n", data->data_len); |
| return false; /* Stop parsing */ |
| } |
| |
| peer_tmap_role = net_buf_simple_pull_le16(&tmas_svc_data); |
| if (!(sys_le16_to_cpu(peer_tmap_role) & BT_TMAP_ROLE_UMR)) { |
| printk("No TMAS UMR support!\n"); |
| return false; /* Stop parsing */ |
| } |
| |
| printk("Attempt to connect!\n"); |
| err = bt_le_scan_stop(); |
| if (err != 0) { |
| printk("Failed to stop scan: %d\n", err); |
| return false; |
| } |
| |
| err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, |
| BT_LE_CONN_PARAM_DEFAULT, |
| &default_conn); |
| if (err != 0) { |
| printk("Create conn to failed (%u)\n", err); |
| start_scan(); |
| } |
| |
| return false; /* Stop parsing */ |
| } |
| |
| static void scan_recv(const struct bt_le_scan_recv_info *info, |
| struct net_buf_simple *buf) |
| { |
| char le_addr[BT_ADDR_LE_STR_LEN]; |
| |
| if (default_conn != NULL) { |
| /* Already connected */ |
| return; |
| } |
| |
| /* Check for connectable, extended advertising */ |
| if (((info->adv_props & BT_GAP_ADV_PROP_EXT_ADV) != 0) || |
| ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE)) != 0) { |
| bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr)); |
| printk("[DEVICE]: %s, ", le_addr); |
| /* Check for TMAS support in advertising data */ |
| bt_data_parse(buf, check_audio_support_and_connect, (void *)info->addr); |
| } |
| } |
| |
| static struct bt_le_scan_cb scan_callbacks = { |
| .recv = scan_recv, |
| }; |
| |
| static void start_scan(void) |
| { |
| int err; |
| |
| err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL); |
| if (err != 0) { |
| printk("Scanning failed to start (err %d)\n", err); |
| return; |
| } |
| |
| printk("Scanning successfully started\n"); |
| } |
| |
| static int scan_and_connect(void) |
| { |
| int err; |
| |
| start_scan(); |
| |
| err = k_sem_take(&sem_connected, K_FOREVER); |
| if (err != 0) { |
| printk("failed to take sem_connected (err %d)\n", err); |
| return err; |
| } |
| |
| err = k_sem_take(&sem_mtu_exchanged, K_FOREVER); |
| if (err != 0) { |
| printk("failed to take sem_mtu_exchanged (err %d)\n", err); |
| return err; |
| } |
| |
| err = bt_conn_set_security(default_conn, BT_SECURITY_L2); |
| if (err != 0) { |
| printk("failed to set security (err %d)\n", err); |
| return err; |
| } |
| |
| err = k_sem_take(&sem_security_updated, K_FOREVER); |
| if (err != 0) { |
| printk("failed to take sem_security_updated (err %d)\n", err); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int main(void) |
| { |
| int err; |
| |
| err = init(); |
| if (err != 0) { |
| return err; |
| } |
| |
| printk("Initializing TMAP and setting role\n"); |
| /* Initialize TMAP */ |
| err = bt_tmap_register(BT_TMAP_ROLE_CG | BT_TMAP_ROLE_UMS); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* Initialize CAP Initiator */ |
| err = cap_initiator_init(); |
| if (err != 0) { |
| return err; |
| } |
| printk("CAP initialized\n"); |
| |
| /* Initialize VCP Volume Controller */ |
| err = vcp_vol_ctlr_init(); |
| if (err != 0) { |
| return err; |
| } |
| printk("VCP initialized\n"); |
| |
| /* Initialize MCP Server */ |
| err = mcp_server_init(); |
| if (err != 0) { |
| return err; |
| } |
| printk("MCP initialized\n"); |
| |
| /* Initialize CCP Server */ |
| err = ccp_server_init(); |
| if (err != 0) { |
| return err; |
| } |
| printk("CCP initialized\n"); |
| |
| /* Register scan callback and start scanning */ |
| bt_le_scan_cb_register(&scan_callbacks); |
| err = scan_and_connect(); |
| if (err != 0) { |
| return err; |
| } |
| |
| err = bt_tmap_discover(default_conn, &tmap_callbacks); |
| if (err != 0) { |
| return err; |
| } |
| |
| k_sem_take(&sem_discovery_done, K_FOREVER); |
| |
| /* Send a VCP command */ |
| err = vcp_vol_ctlr_mute(); |
| if (err != 0) { |
| printk("Error sending mute command!\n"); |
| } |
| |
| /* Discover and configure unicast streams */ |
| err = cap_initiator_setup(default_conn); |
| if (err != 0) { |
| return err; |
| } |
| |
| return 0; |
| } |