diff --git a/samples/bluetooth/handsfree_ag/CMakeLists.txt b/samples/bluetooth/handsfree_ag/CMakeLists.txt new file mode 100644 index 00000000000..af90adf8b82 --- /dev/null +++ b/samples/bluetooth/handsfree_ag/CMakeLists.txt @@ -0,0 +1,10 @@ +#SPDX - License - Identifier : Apache - 2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(handsfree_ag) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/samples/bluetooth/handsfree_ag/Kconfig b/samples/bluetooth/handsfree_ag/Kconfig new file mode 100644 index 00000000000..f3a1edbcdc9 --- /dev/null +++ b/samples/bluetooth/handsfree_ag/Kconfig @@ -0,0 +1,24 @@ +# +# Copyright 2024 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +mainmenu "Bluetooth: handsfree AG" + +config BT_HFP_AG_DISCOVER_RESULT_COUNT + int "Maximum result count per device discovery" + default 10 + +config BT_HFP_AG_CALL_OUTGOING + bool "The simulate call: outgoing (y), incoming (n)" + +config BT_HFP_AG_START_CALL_DELAY_TIME + int "The delay time used to start simulating a call after AG connection" + default 5000 + help + The Delay time is used to wait for the peer to start dialing. If the + peer does not dial within the timeout period, AG satrt simulating a + call. The unit is ms. + +source "Kconfig.zephyr" diff --git a/samples/bluetooth/handsfree_ag/README.rst b/samples/bluetooth/handsfree_ag/README.rst new file mode 100644 index 00000000000..9d2e2d9343a --- /dev/null +++ b/samples/bluetooth/handsfree_ag/README.rst @@ -0,0 +1,23 @@ +.. _bt_handsfree_ag: + +Bluetooth: Handsfree Audio Gateway +################################## + +Overview +******** + +Application demonstrating usage of the Hands-free Audio Gateway (AG) APIs. + +Requirements +************ + +* Running on the host with Bluetooth BR/EDR (Classic) support, or +* A board with Bluetooth BR/EDR (Classic) support + +Building and Running +******************** + +This sample can be found under :zephyr_file:`samples/bluetooth/handsfree_ag` in +the Zephyr tree. + +See :ref:`bluetooth samples section ` for details. diff --git a/samples/bluetooth/handsfree_ag/prj.conf b/samples/bluetooth/handsfree_ag/prj.conf new file mode 100644 index 00000000000..cd315ed26b8 --- /dev/null +++ b/samples/bluetooth/handsfree_ag/prj.conf @@ -0,0 +1,6 @@ +CONFIG_BT=y +CONFIG_BT_CLASSIC=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_HFP_AG=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_DEVICE_NAME="Handsfree-ag" diff --git a/samples/bluetooth/handsfree_ag/sample.yaml b/samples/bluetooth/handsfree_ag/sample.yaml new file mode 100644 index 00000000000..416834fc191 --- /dev/null +++ b/samples/bluetooth/handsfree_ag/sample.yaml @@ -0,0 +1,13 @@ +sample: + name: Bluetooth Handsfree Audio Gateway +tests: + sample.bluetooth.handsfree.ag: + harness: bluetooth + platform_allow: + - qemu_cortex_m3 + - qemu_x86 + tags: bluetooth + integration_platforms: + - qemu_cortex_m3 + extra_configs: + - CONFIG_BT_HFP_AG_CALL_OUTGOING=y diff --git a/samples/bluetooth/handsfree_ag/src/main.c b/samples/bluetooth/handsfree_ag/src/main.c new file mode 100644 index 00000000000..03c76eb0805 --- /dev/null +++ b/samples/bluetooth/handsfree_ag/src/main.c @@ -0,0 +1,384 @@ +/* main.c - Application main entry point */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static struct bt_conn *default_conn; +struct bt_hfp_ag *hfp_ag; + +static struct bt_br_discovery_param br_discover; + +static struct bt_br_discovery_param br_discover; +static struct bt_br_discovery_result scan_result[CONFIG_BT_HFP_AG_DISCOVER_RESULT_COUNT]; + +struct k_work discover_work; +struct k_work_delayable call_connect_work; +struct k_work_delayable call_disconnect_work; + +struct k_work_delayable call_remote_ringing_work; +struct k_work_delayable call_remote_accept_work; + +NET_BUF_POOL_DEFINE(sdp_discover_pool, 10, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +static void ag_connected(struct bt_hfp_ag *ag) +{ + printk("HFP AG connected!\n"); + k_work_schedule(&call_connect_work, K_MSEC(CONFIG_BT_HFP_AG_START_CALL_DELAY_TIME)); +} + +static void ag_disconnected(struct bt_hfp_ag *ag) +{ + printk("HFP AG disconnected!\n"); +} + +static void ag_sco_connected(struct bt_hfp_ag *ag, struct bt_conn *sco_conn) +{ + printk("HFP AG SCO connected!\n"); +} + +static void ag_sco_disconnected(struct bt_hfp_ag *ag) +{ + printk("HFP AG SCO disconnected!\n"); +} + +static void ag_ringing(struct bt_hfp_ag *ag, bool in_band) +{ + printk("Ringing (in bond? %s)\n", in_band ? "Yes" : "No"); +} + +static void ag_accept(struct bt_hfp_ag *ag) +{ + printk("Call Accepted\n"); + k_work_schedule(&call_disconnect_work, K_SECONDS(10)); +} + +static void ag_reject(struct bt_hfp_ag *ag) +{ + printk("Call Rejected\n"); + k_work_schedule(&call_disconnect_work, K_SECONDS(1)); +} + +static void ag_terminate(struct bt_hfp_ag *ag) +{ + printk("Call terminated\n"); + k_work_schedule(&call_disconnect_work, K_SECONDS(1)); +} + +static void ag_outgoing(struct bt_hfp_ag *ag, const char *number) +{ + printk("Call outgoing, remote number %s\n", number); + k_work_cancel_delayable(&call_connect_work); + k_work_schedule(&call_remote_ringing_work, K_SECONDS(1)); +} + +static void ag_incoming(struct bt_hfp_ag *ag, const char *number) +{ + printk("Incoming call, remote number %s\n", number); + k_work_cancel_delayable(&call_connect_work); +} + +static struct bt_hfp_ag_cb ag_cb = { + .connected = ag_connected, + .disconnected = ag_disconnected, + .sco_connected = ag_sco_connected, + .sco_disconnected = ag_sco_disconnected, + .outgoing = ag_outgoing, + .incoming = ag_incoming, + .ringing = ag_ringing, + .accept = ag_accept, + .reject = ag_reject, + .terminate = ag_terminate, +}; + +static uint8_t sdp_discover_cb(struct bt_conn *conn, struct bt_sdp_client_result *result) +{ + int err; + uint16_t value; + + printk("Discover done\n"); + + if (result->resp_buf != NULL) { + err = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_RFCOMM, &value); + + if (err != 0) { + printk("Fail to parser RFCOMM the SDP response!\n"); + } else { + printk("The server channel is %d\n", value); + err = bt_hfp_ag_connect(conn, &hfp_ag, value); + if (err != 0) { + printk("Fail to create hfp AG connection (err %d)\n", err); + } + } + } + + return BT_SDP_DISCOVER_UUID_STOP; +} + +static struct bt_sdp_discover_params sdp_discover = { + .func = sdp_discover_cb, + .pool = &sdp_discover_pool, + .uuid = BT_UUID_DECLARE_16(BT_SDP_HANDSFREE_SVCLASS), +}; + +static void connected(struct bt_conn *conn, uint8_t err) +{ + int res; + + if (err) { + if (default_conn != NULL) { + default_conn = NULL; + } + printk("Connection failed (err 0x%02x)\n", err); + } else { + if (default_conn == conn) { + struct bt_conn_info info; + + bt_conn_get_info(conn, &info); + if (info.type != BT_CONN_TYPE_BR) { + return; + } + + /* + * Do an SDP Query on Successful ACL connection complete with the + * required device + */ + res = bt_sdp_discover(default_conn, &sdp_discover); + if (res) { + printk("SDP discovery failed (err %d)\r\n", res); + } else { + printk("SDP discovery started\r\n"); + } + printk("Connected\n"); + } + } +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + printk("Disconnected (reason 0x%02x)\n", reason); + + if (default_conn != conn) { + return; + } + + if (default_conn) { + default_conn = NULL; + } else { + return; + } +} + +static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + struct bt_conn_info info; + + bt_conn_get_info(conn, &info); + + bt_addr_to_str(info.br.dst, addr, sizeof(addr)); + + printk("Security changed: %s level %u (err %d)\n", addr, level, err); +} + +static struct bt_conn_cb conn_callbacks = { + .connected = connected, + .disconnected = disconnected, + .security_changed = security_changed, +}; + +static void scan_discovery_cb(struct bt_br_discovery_result *results, size_t count) +{ + char addr[BT_ADDR_LE_STR_LEN]; + uint8_t *eir; + bool cod_hf = false; + static uint8_t temp[240]; + size_t len = sizeof(results->eir); + uint8_t major_device; + uint8_t minor_device; + size_t i; + + for (i = 0; i < count; i++) { + bt_addr_to_str(&results[i].addr, addr, sizeof(addr)); + printk("Device[%d]: %s, rssi %d, cod 0x%X%X%X", i, addr, results[i].rssi, + results[i].cod[0], results[i].cod[1], results[i].cod[2]); + + major_device = (uint8_t)BT_COD_MAJOR_DEVICE_CLASS(results[i].cod); + minor_device = (uint8_t)BT_COD_MINOR_DEVICE_CLASS(results[i].cod); + + if ((major_device & BT_COD_MAJOR_AUDIO_VIDEO) && + (minor_device & BT_COD_MAJOR_AUDIO_VIDEO_MINOR_HANDS_FREE)) { + cod_hf = true; + } + + eir = results[i].eir; + + while ((eir[0] > 2) && (len > eir[0])) { + switch (eir[1]) { + case BT_DATA_NAME_SHORTENED: + case BT_DATA_NAME_COMPLETE: + memcpy(temp, &eir[2], eir[0] - 1); + temp[eir[0] - 1] = '\0'; /* Set end flag */ + printk(", name %s", temp); + break; + } + len = len - eir[0] - 1; + eir = eir + eir[0] + 1; + } + printk("\n"); + + if (cod_hf) { + break; + } + } + + if (!cod_hf) { + (void)k_work_submit(&discover_work); + } else { + (void)k_work_cancel(&discover_work); + default_conn = bt_conn_create_br(&results[i].addr, BT_BR_CONN_PARAM_DEFAULT); + + if (default_conn == NULL) { + printk("Fail to create the connecton\n"); + } else { + bt_conn_unref(default_conn); + } + } +} + +static void discover_work_handler(struct k_work *work) +{ + int err; + + br_discover.length = 10; + br_discover.limited = false; + + err = bt_br_discovery_start(&br_discover, scan_result, + CONFIG_BT_HFP_AG_DISCOVER_RESULT_COUNT, scan_discovery_cb); + if (err) { + printk("Fail to start discovery (err %d)\n", err); + return; + } +} + +static void call_connect_work_handler(struct k_work *work) +{ +#if CONFIG_BT_HFP_AG_CALL_OUTGOING + int err; + + printk("Dialing\n"); + + err = bt_hfp_ag_outgoing(hfp_ag, "test_hf"); + + if (err != 0) { + printk("Fail to dial a call (err %d)\n", err); + } +#else + int err = bt_hfp_ag_remote_incoming(hfp_ag, "test_hf"); + + if (err != 0) { + printk("Fail to set remote incoming call (err %d)\n", err); + } +#endif /* CONFIG_BT_HFP_AG_CALL_OUTGOING */ +} + +static void call_disconnect_work_handler(struct k_work *work) +{ + int err; + + if (default_conn != NULL) { + err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + + if (err != 0) { + printk("Fail to disconnect acl connection (err %d)\n", err); + } + } +} + +static void call_remote_ringing_work_handler(struct k_work *work) +{ + int err; + + printk("Remote starts ringing\n"); + + err = bt_hfp_ag_remote_ringing(hfp_ag); + + if (err != 0) { + printk("Fail to notify hfp unit that the remote starts ringing (err %d)\n", err); + } else { + k_work_schedule(&call_remote_accept_work, K_SECONDS(1)); + } +} + +static void call_remote_accept_work_handler(struct k_work *work) +{ + int err; + + printk("Remote accepts the call\n"); + + err = bt_hfp_ag_remote_accept(hfp_ag); + + if (err != 0) { + printk("Fail to notify hfp unit that the remote accepts call (err %d)\n", err); + } +} + +static void bt_ready(int err) +{ + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + return; + } + + if (IS_ENABLED(CONFIG_SETTINGS)) { + settings_load(); + } + + printk("Bluetooth initialized\n"); + + bt_conn_cb_register(&conn_callbacks); + + bt_hfp_ag_register(&ag_cb); + + k_work_init(&discover_work, discover_work_handler); + + (void)k_work_submit(&discover_work); + + k_work_init_delayable(&call_connect_work, call_connect_work_handler); + k_work_init_delayable(&call_disconnect_work, call_disconnect_work_handler); + + k_work_init_delayable(&call_remote_ringing_work, call_remote_ringing_work_handler); + k_work_init_delayable(&call_remote_accept_work, call_remote_accept_work_handler); +} + +int main(void) +{ + int err; + + printk("Bluetooth Handsfree AG demo start...\n"); + + err = bt_enable(bt_ready); + if (err) { + printk("Bluetooth init failed (err %d)\n", err); + } + return 0; +}