samples: Bluetooth: HFP_AG: Initialize version
Add a new example handsfree_ag to demonstrate usage of the handsfree audio gateway APIs. Signed-off-by: Lyle Zhu <lyle.zhu@nxp.com>
This commit is contained in:
parent
514c78546c
commit
4c7b19fa5a
6 changed files with 460 additions and 0 deletions
10
samples/bluetooth/handsfree_ag/CMakeLists.txt
Normal file
10
samples/bluetooth/handsfree_ag/CMakeLists.txt
Normal file
|
@ -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)
|
24
samples/bluetooth/handsfree_ag/Kconfig
Normal file
24
samples/bluetooth/handsfree_ag/Kconfig
Normal file
|
@ -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"
|
23
samples/bluetooth/handsfree_ag/README.rst
Normal file
23
samples/bluetooth/handsfree_ag/README.rst
Normal file
|
@ -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 <bluetooth-samples>` for details.
|
6
samples/bluetooth/handsfree_ag/prj.conf
Normal file
6
samples/bluetooth/handsfree_ag/prj.conf
Normal file
|
@ -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"
|
13
samples/bluetooth/handsfree_ag/sample.yaml
Normal file
13
samples/bluetooth/handsfree_ag/sample.yaml
Normal file
|
@ -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
|
384
samples/bluetooth/handsfree_ag/src/main.c
Normal file
384
samples/bluetooth/handsfree_ag/src/main.c
Normal file
|
@ -0,0 +1,384 @@
|
|||
/* main.c - Application main entry point */
|
||||
|
||||
/*
|
||||
* Copyright 2024 NXP
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/types.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/bluetooth/l2cap.h>
|
||||
#include <zephyr/bluetooth/classic/rfcomm.h>
|
||||
#include <zephyr/bluetooth/classic/sdp.h>
|
||||
#include <zephyr/bluetooth/classic/hfp_ag.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
|
||||
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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue