diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index c0c4fe5c556c6..0f4e3c8dc7752 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -402,6 +402,7 @@ Bluetooth Host: - doc/connectivity/bluetooth/shell/audio/ - samples/bluetooth/bap*/ - samples/bluetooth/cap*/ + - samples/bluetooth/ccp*/ - samples/bluetooth/hap*/ - samples/bluetooth/hci_*/ - samples/bluetooth/pbp*/ @@ -486,6 +487,7 @@ Bluetooth Audio: - doc/connectivity/bluetooth/shell/audio/ - samples/bluetooth/bap*/ - samples/bluetooth/cap*/ + - samples/bluetooth/ccp*/ - samples/bluetooth/hap*/ - samples/bluetooth/pbp*/ - samples/bluetooth/tmap*/ diff --git a/doc/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.rst b/doc/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.rst index 1156f2a4e89e9..7eac2171a3280 100644 --- a/doc/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.rst +++ b/doc/connectivity/bluetooth/api/audio/bluetooth-le-audio-arch.rst @@ -294,6 +294,7 @@ GAF and the top layer profiles gave been implemented in Zephyr with the followin cluster=true; label="CCP"; style=solid; + CCP_H [label="ccp.h"]; TBS_H [label="tbs.h"]; } } @@ -331,6 +332,7 @@ GAF and the top layer profiles gave been implemented in Zephyr with the followin CAP_H -> MCS_H; CAP_H -> MCC_H; CAP_H -> MP_H; + CAP_H -> CCP_H; CAP_H -> TBS_H; CAP_H -> BAP_H; CAP_H -> BAP_PRESET_H; @@ -341,6 +343,7 @@ GAF and the top layer profiles gave been implemented in Zephyr with the followin CSIP_H -> MCS_H; CSIP_H -> MCC_H; CSIP_H -> MP_H; + CSIP_H -> CCP_H; CSIP_H -> TBS_H; CSIP_H -> BAP_H; CSIP_H -> BAP_PRESET_H; @@ -719,8 +722,8 @@ Bluetooth Audio Stack. | | | | | - Shell Module | | | | | | | - BSIM test | | +--------+-------------------------------+---------+------------------+-----------------------+--------------------------------------------------+ - | CCP | Call Control Server | 1.0 | 3.0 | - Feature complete | - API refactor | - | | | | | - Shell Module | - Sample Application | + | CCP | Call Control Server | 1.0 | 3.0 | - Feature complete | - API refactor (in progress) | + | | | | | - Shell Module | - Sample Application (in progress) | | | | | | - BSIM test | | | +-------------------------------+---------+------------------+-----------------------+--------------------------------------------------+ | | Call Control Client | 1.0 | 3.0 | - Feature complete | - API refactor | diff --git a/doc/connectivity/bluetooth/bluetooth-shell.rst b/doc/connectivity/bluetooth/bluetooth-shell.rst index 1cfa5d8054689..95fda5c2f61d9 100644 --- a/doc/connectivity/bluetooth/bluetooth-shell.rst +++ b/doc/connectivity/bluetooth/bluetooth-shell.rst @@ -19,6 +19,7 @@ For specific Bluetooth functionality see also the following shell documentation shell/audio/csip.rst shell/audio/gmap.rst shell/audio/mcp.rst + shell/audio/tbs.rst shell/audio/tmap.rst shell/audio/pbp.rst shell/classic/a2dp.rst diff --git a/doc/connectivity/bluetooth/shell/audio/ccp.rst b/doc/connectivity/bluetooth/shell/audio/ccp.rst index 9d5c17c4e7c9c..1dc256a78befe 100644 --- a/doc/connectivity/bluetooth/shell/audio/ccp.rst +++ b/doc/connectivity/bluetooth/shell/audio/ccp.rst @@ -1,208 +1,24 @@ Bluetooth: Call Control Profile Shell ##################################### -This document describes how to run the call control functionality, both as -a client and as a (telephone bearer service (TBS)) server. Note that in the -examples below, some lines of debug have been removed to make this shorter -and provide a better overview. +Call Control Server +******************* +The Call Control Server is a role that typically resides on devices that can make calls, +including calls from apps such as Skype, e.g. (smart)phones and PCs, +which are typically GAP Central devices. -Telephone Bearer Service Client -******************************* - -The telephone bearer service client will typically exist on a resource -restricted device, such as headphones, but may also exist on e.g. phones or -laptops. The call control client will also thus typically be the advertiser. -The client can control the states of calls on a server using the call control -point. - -It is necessary to have :kconfig:option:`CONFIG_BT_TBS_CLIENT_LOG_LEVEL_DBG` -enabled for using the client interactively. - -Using the telephone bearer service client -========================================= - -When the Bluetooth stack has been initialized (:code:`bt init`), -and a device has been connected, the telephone bearer service client can -discover TBS on the connected device calling :code:`tbs_client discover`, which -will start a discovery for the TBS UUIDs and store the handles, and optionally -subscribe to all notifications (default is to subscribe to all). - -Since a server may have multiple TBS instances, most of the tbs_client commands -will take an index (starting from 0) as input. Joining calls require at least 2 -call IDs, and all call indexes shall be on the same TBS instance. - -A server will also have a GTBS instance, which is an abstraction layer for all -the telephone bearers on the server. If the server has both GTBS and TBS, -the client may subscribe and use either when sending requests if -:code:`BT_TBS_CLIENT_GTBS` is enabled. - -.. code-block:: console - - tbs_client --help - tbs_client - Bluetooth TBS_CLIENT shell commands - Subcommands: - discover :Discover TBS [subscribe] - set_signal_reporting_interval :Set the signal reporting interval - [<{instance_index, gtbs}>] - originate :Originate a call [<{instance_index, gtbs}>] - - terminate :terminate a call [<{instance_index, gtbs}>] - - accept :Accept a call [<{instance_index, gtbs}>] - hold :Place a call on hold [<{instance_index, - gtbs}>] - retrieve :Retrieve a held call [<{instance_index, - gtbs}>] - read_provider_name :Read the bearer name [<{instance_index, - gtbs}>] - read_bearer_uci :Read the bearer UCI [<{instance_index, gtbs}>] - read_technology :Read the bearer technology [<{instance_index, - gtbs}>] - read_uri_list :Read the bearer's supported URI list - [<{instance_index, gtbs}>] - read_signal_strength :Read the bearer signal strength - [<{instance_index, gtbs}>] - read_signal_interval :Read the bearer signal strength reporting - interval [<{instance_index, gtbs}>] - read_current_calls :Read the current calls [<{instance_index, - gtbs}>] - read_ccid :Read the CCID [<{instance_index, gtbs}>] - read_status_flags :Read the in feature and status value - [<{instance_index, gtbs}>] - read_uri :Read the incoming call target URI - [<{instance_index, gtbs}>] - read_call_state :Read the call state [<{instance_index, gtbs}>] - read_remote_uri :Read the incoming remote URI - [<{instance_index, gtbs}>] - read_friendly_name :Read the friendly name of an incoming call - [<{instance_index, gtbs}>] - read_optional_opcodes :Read the optional opcodes [<{instance_index, - gtbs}>] - - -In the following examples, notifications from GTBS is ignored, unless otherwise -specified. - -Example usage -============= - -Setup ------ - -.. code-block:: console - - uart:~$ bt init - uart:~$ bt advertise on - Advertising started - -When connected --------------- - -Placing a call: - -.. code-block:: console - - uart:~$ tbs_client discover - bt_tbs_client.primary_discover_func: Discover complete, found 1 instances (GTBS found) - bt_tbs_client.discover_func: Setup complete for 1 / 1 TBS - bt_tbs_client.discover_func: Setup complete GTBS - uart:~$ tbs_client originate 0 tel:123 - bt_tbs_client.notify_handler: Index 0 - bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the dialing state with URI tel:123 - bt_tbs_client.call_cp_notify_handler: Status: success for the originate opcode for call 0x00 - bt_tbs_client.notify_handler: Index 0 - bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the alerting state with URI tel:123 - - bt_tbs_client.notify_handler: Index 0 - bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the active state with URI tel:123 - -Placing a call on GTBS: - -.. code-block:: console - - uart:~$ tbs_client originate 0 tel:123 - bt_tbs_client.notify_handler: Index 0 - bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the dialing state with URI tel:123 - bt_tbs_client.call_cp_notify_handler: Status: success for the originate opcode for call 0x00 - bt_tbs_client.notify_handler: Index 0 - bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the alerting state with URI tel:123 - - bt_tbs_client.notify_handler: Index 0 - bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the active state with URI tel:123 - -It is necessary to set an outgoing caller ID before placing a call. - -Accepting incoming call from peer device: - -.. code-block:: console - - bt_tbs_client.incoming_uri_notify_handler: tel:123 - bt_tbs_client.in_call_notify_handler: tel:456 - bt_tbs_client.friendly_name_notify_handler: Peter - bt_tbs_client.current_calls_notify_handler: Call 0x05 is in the incoming state with URI tel:456 - uart:~$ tbs_client accept 0 5 - bt_tbs_client.call_cp_callback_handler: Status: success for the accept opcode for call 0x05 - bt_tbs_client.current_calls_notify_handler: Call 0x05 is in the active state with URI tel - - -Terminate call: - -.. code-block:: console - - uart:~$ tbs_client terminate 0 5 - bt_tbs_client.termination_reason_notify_handler: ID 0x05, reason 0x06 - bt_tbs_client.call_cp_notify_handler: Status: success for the terminate opcode for call 0x05 - bt_tbs_client.current_calls_notify_handler: - -Telephone Bearer Service (TBS) -****************************** -The telephone bearer service is a service that typically resides on devices that -can make calls, including calls from apps such as Skype, e.g. (smart)phones and -PCs. - -It is necessary to have :kconfig:option:`CONFIG_BT_TBS_LOG_LEVEL_DBG` enabled -for using the TBS server interactively. - -Using the telephone bearer service -================================== -TBS can be controlled locally, or by a remote device (when in a call). For -example a remote device may initiate a call to the device with the TBS server, -or the TBS server may initiate a call to remote device, without a TBS_CLIENT client. -The TBS implementation is capable of fully controlling any call. -Omitting an index for commands where a :code:`` can be supplied, defaults to the -GTBS bearer. +Using the Call Control Server +============================= +The Server can be controlled locally, or by a remote device (when in a call). For +example a remote device may initiate a call to the server, +or the Server may initiate a call to remote device, without a client. .. code-block:: console - tbs --help - tbs - Bluetooth TBS shell commands + ccp_call_control_server --help + ccp_call_control_server - Bluetooth CCP Call Control Server shell commands Subcommands: - init :Initialize TBS - authorize :Authorize the current connection - accept :Accept call - terminate :Terminate call - hold :Hold call - retrieve :Retrieve call - originate :Originate call [] - join :Join calls [ [ [...]]] - incoming :Simulate incoming remote call [<{instance_index, - gtbs}>] - - remote_answer :Simulate remote answer outgoing call - remote_retrieve :Simulate remote retrieve - remote_terminate :Simulate remote terminate - remote_hold :Simulate remote hold - set_bearer_provider_name :Set the bearer provider name [<{instance_index, - gtbs}>] - set_bearer_technology :Set the bearer technology [<{instance_index, - gtbs}>] - set_bearer_signal_strength :Set the bearer signal strength [<{instance_index, - gtbs}>] - set_status_flags :Set the bearer feature and status value - [<{instance_index, gtbs}>] - set_uri_scheme :Set the URI prefix list - print_calls :Output all calls in the debug log + init : Initialize CCP Call Control Server Example Usage ============= @@ -213,26 +29,7 @@ Setup .. code-block:: console uart:~$ bt init + uart:~$ ccp_call_control_server init + Registered GTBS bearer + Registered bearer[1] uart:~$ bt connect xx:xx:xx:xx:xx:xx public - -When connected --------------- - -Answering a call for a peer device originated by a client: - -.. code-block:: console - - bt_tbs.write_call_cp: Index 0: Processing the originate opcode - bt_tbs.originate_call: New call with call index 1 - bt_tbs.write_call_cp: Index 0: Processed the originate opcode with status success for call index 1 - uart:~$ tbs remote_answer 1 - TBS succeeded for call_id: 1 - -Incoming call from a peer device, accepted by client: - -.. code-block:: console - - uart:~$ tbs incoming 0 tel:123 tel:456 Peter - TBS succeeded for call_id: 4 - bt_tbs.bt_tbs_remote_incoming: New call with call index 4 - bt_tbs.write_call_cp: Index 0: Processed the accept opcode with status success for call index 4 diff --git a/doc/connectivity/bluetooth/shell/audio/tbs.rst b/doc/connectivity/bluetooth/shell/audio/tbs.rst new file mode 100644 index 0000000000000..e4eb3efebf346 --- /dev/null +++ b/doc/connectivity/bluetooth/shell/audio/tbs.rst @@ -0,0 +1,237 @@ +Bluetooth: Telephone Bearer Service Shell +######################################### + +This document describes how to run the call control functionality, both as +a client and as a (telephone bearer service (TBS)) server. Note that in the +examples below, some lines of debug have been removed to make this shorter +and provide a better overview. + +Telephone Bearer Service Client +******************************* + +The telephone bearer service client will typically exist on a resource +restricted device, such as headphones, but may also exist on e.g. phones or +laptops. The call control client will also thus typically be the advertiser. +The client can control the states of calls on a server using the call control +point. + +It is necessary to have :kconfig:option:`CONFIG_BT_TBS_CLIENT_LOG_LEVEL_DBG` +enabled for using the client interactively. + +Using the telephone bearer service client +========================================= + +When the Bluetooth stack has been initialized (:code:`bt init`), +and a device has been connected, the telephone bearer service client can +discover TBS on the connected device calling :code:`tbs_client discover`, which +will start a discovery for the TBS UUIDs and store the handles, and optionally +subscribe to all notifications (default is to subscribe to all). + +Since a server may have multiple TBS instances, most of the tbs_client commands +will take an index (starting from 0) as input. Joining calls require at least 2 +call IDs, and all call indexes shall be on the same TBS instance. + +A server will also have a GTBS instance, which is an abstraction layer for all +the telephone bearers on the server. If the server has both GTBS and TBS, +the client may subscribe and use either when sending requests if +:code:`BT_TBS_CLIENT_GTBS` is enabled. + +.. code-block:: console + + tbs_client --help + tbs_client - Bluetooth TBS_CLIENT shell commands + Subcommands: + discover :Discover TBS [subscribe] + set_signal_reporting_interval :Set the signal reporting interval + [<{instance_index, gtbs}>] + originate :Originate a call [<{instance_index, gtbs}>] + + terminate :terminate a call [<{instance_index, gtbs}>] + + accept :Accept a call [<{instance_index, gtbs}>] + hold :Place a call on hold [<{instance_index, + gtbs}>] + retrieve :Retrieve a held call [<{instance_index, + gtbs}>] + read_provider_name :Read the bearer name [<{instance_index, + gtbs}>] + read_bearer_uci :Read the bearer UCI [<{instance_index, gtbs}>] + read_technology :Read the bearer technology [<{instance_index, + gtbs}>] + read_uri_list :Read the bearer's supported URI list + [<{instance_index, gtbs}>] + read_signal_strength :Read the bearer signal strength + [<{instance_index, gtbs}>] + read_signal_interval :Read the bearer signal strength reporting + interval [<{instance_index, gtbs}>] + read_current_calls :Read the current calls [<{instance_index, + gtbs}>] + read_ccid :Read the CCID [<{instance_index, gtbs}>] + read_status_flags :Read the in feature and status value + [<{instance_index, gtbs}>] + read_uri :Read the incoming call target URI + [<{instance_index, gtbs}>] + read_call_state :Read the call state [<{instance_index, gtbs}>] + read_remote_uri :Read the incoming remote URI + [<{instance_index, gtbs}>] + read_friendly_name :Read the friendly name of an incoming call + [<{instance_index, gtbs}>] + read_optional_opcodes :Read the optional opcodes [<{instance_index, + gtbs}>] + + +In the following examples, notifications from GTBS are ignored, unless otherwise specified. + +Example usage +============= + +Setup +----- + +.. code-block:: console + + uart:~$ bt init + uart:~$ bt advertise on + Advertising started + +When connected +-------------- + +Placing a call: + +.. code-block:: console + + uart:~$ tbs_client discover + bt_tbs_client.primary_discover_func: Discover complete, found 1 instances (GTBS found) + bt_tbs_client.discover_func: Setup complete for 1 / 1 TBS + bt_tbs_client.discover_func: Setup complete GTBS + uart:~$ tbs_client originate 0 tel:123 + bt_tbs_client.notify_handler: Index 0 + bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the dialing state with URI tel:123 + bt_tbs_client.call_cp_notify_handler: Status: success for the originate opcode for call 0x00 + bt_tbs_client.notify_handler: Index 0 + bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the alerting state with URI tel:123 + + bt_tbs_client.notify_handler: Index 0 + bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the active state with URI tel:123 + +Placing a call on GTBS: + +.. code-block:: console + + uart:~$ tbs_client originate 0 tel:123 + bt_tbs_client.notify_handler: Index 0 + bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the dialing state with URI tel:123 + bt_tbs_client.call_cp_notify_handler: Status: success for the originate opcode for call 0x00 + bt_tbs_client.notify_handler: Index 0 + bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the alerting state with URI tel:123 + + bt_tbs_client.notify_handler: Index 0 + bt_tbs_client.current_calls_notify_handler: Call 0x01 is in the active state with URI tel:123 + +It is necessary to set an outgoing caller ID before placing a call. + +Accepting incoming call from peer device: + +.. code-block:: console + + bt_tbs_client.incoming_uri_notify_handler: tel:123 + bt_tbs_client.in_call_notify_handler: tel:456 + bt_tbs_client.friendly_name_notify_handler: Peter + bt_tbs_client.current_calls_notify_handler: Call 0x05 is in the incoming state with URI tel:456 + uart:~$ tbs_client accept 0 5 + bt_tbs_client.call_cp_callback_handler: Status: success for the accept opcode for call 0x05 + bt_tbs_client.current_calls_notify_handler: Call 0x05 is in the active state with URI tel + + +Terminate call: + +.. code-block:: console + + uart:~$ tbs_client terminate 0 5 + bt_tbs_client.termination_reason_notify_handler: ID 0x05, reason 0x06 + bt_tbs_client.call_cp_notify_handler: Status: success for the terminate opcode for call 0x05 + bt_tbs_client.current_calls_notify_handler: + +Telephone Bearer Service (TBS) +****************************** +The telephone bearer service is a service that typically resides on devices that +can make calls, including calls from apps such as Skype, e.g. (smart)phones and +PCs. + +It is necessary to have :kconfig:option:`CONFIG_BT_TBS_LOG_LEVEL_DBG` enabled +for using the TBS server interactively. + +Using the telephone bearer service +================================== +TBS can be controlled locally, or by a remote device (when in a call). For +example a remote device may initiate a call to the device with the TBS server, +or the TBS server may initiate a call to remote device, without a TBS_CLIENT client. +The TBS implementation is capable of fully controlling any call. +Omitting an index for commands where a :code:`` can be supplied, defaults to the +GTBS bearer. + +.. code-block:: console + + tbs --help + tbs - Bluetooth TBS shell commands + Subcommands: + init :Initialize TBS + authorize :Authorize the current connection + accept :Accept call + terminate :Terminate call + hold :Hold call + retrieve :Retrieve call + originate :Originate call [] + join :Join calls [ [ [...]]] + incoming :Simulate incoming remote call [<{instance_index, + gtbs}>] + + remote_answer :Simulate remote answer outgoing call + remote_retrieve :Simulate remote retrieve + remote_terminate :Simulate remote terminate + remote_hold :Simulate remote hold + set_bearer_provider_name :Set the bearer provider name [<{instance_index, + gtbs}>] + set_bearer_technology :Set the bearer technology [<{instance_index, + gtbs}>] + set_bearer_signal_strength :Set the bearer signal strength [<{instance_index, + gtbs}>] + set_status_flags :Set the bearer feature and status value + [<{instance_index, gtbs}>] + set_uri_scheme :Set the URI prefix list + print_calls :Output all calls in the debug log + +Example Usage +============= + +Setup +----- + +.. code-block:: console + + uart:~$ bt init + uart:~$ bt connect xx:xx:xx:xx:xx:xx public + +When connected +-------------- + +Answering a call for a peer device originated by a client: + +.. code-block:: console + + bt_tbs.write_call_cp: Index 0: Processing the originate opcode + bt_tbs.originate_call: New call with call index 1 + bt_tbs.write_call_cp: Index 0: Processed the originate opcode with status success for call index 1 + uart:~$ tbs remote_answer 1 + TBS succeeded for call_id: 1 + +Incoming call from a peer device, accepted by client: + +.. code-block:: console + + uart:~$ tbs incoming 0 tel:123 tel:456 Peter + TBS succeeded for call_id: 4 + bt_tbs.bt_tbs_remote_incoming: New call with call index 4 + bt_tbs.write_call_cp: Index 0: Processed the accept opcode with status success for call index 4 diff --git a/include/zephyr/bluetooth/audio/ccp.h b/include/zephyr/bluetooth/audio/ccp.h new file mode 100644 index 0000000000000..4bd8b53fd8c72 --- /dev/null +++ b/include/zephyr/bluetooth/audio/ccp.h @@ -0,0 +1,101 @@ +/** + * @file + * @brief Bluetooth Call Control Profile (CCP) APIs. + */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_CCP_H_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_CCP_H_ + +/** + * @brief Call Control Profile (CCP) + * + * @defgroup bt_ccp Call Control Profile (CCP) + * + * @since 3.7 + * @version 0.1.0 + * + * @ingroup bluetooth + * @{ + * + * Call Control Profile (CCP) provides procedures to initiate and control calls. + * It provides the Call Control Client and the Call Control Server roles, + * where the former is usually placed on resource constrained devices like headphones, + * and the latter placed on more powerful devices like phones and PCs. + * + * The profile is not limited to carrier phone calls and can be used with common applications like + * Discord and Teams. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @defgroup bt_ccp_call_control_server CCP Call Control Server APIs + * @ingroup bt_ccp + * @{ + */ +/** @brief Abstract Call Control Server Telephone Bearer structure. */ +struct bt_ccp_call_control_server_bearer; + +/** + * @brief Register a Telephone Bearer + * + * This will register a Telephone Bearer Service (TBS) (or a Generic Telephone Bearer service + * (GTBS)) with the provided parameters. + * + * As per the TBS specification, the GTBS shall be instantiated for the feature, + * and as such a GTBS shall always be registered before any TBS can be registered. + * Similarly, all TBS shall be unregistered before the GTBS can be unregistered with + * bt_ccp_call_control_server_unregister_bearer(). + * + * @param[in] param The parameters to initialize the bearer. + * @param[out] bearer Pointer to the initialized bearer. + * + * @retval 0 Success + * @retval -EINVAL @p param contains invalid data + * @retval -EALREADY @p param.gtbs is true and GTBS has already been registered + * @retval -EAGAIN @p param.gtbs is false and GTBS has not been registered + * @retval -ENOMEM @p param.gtbs is false and no more TBS can be registered (see + * @kconfig{CONFIG_BT_TBS_BEARER_COUNT}) + * @retval -ENOEXEC The service failed to be registered + */ +int bt_ccp_call_control_server_register_bearer(const struct bt_tbs_register_param *param, + struct bt_ccp_call_control_server_bearer **bearer); + +/** + * @brief Unregister a Telephone Bearer + * + * This will unregister a Telephone Bearer Service (TBS) (or a Generic Telephone Bearer service + * (GTBS)) with the provided parameters. The bearer shall be registered first by + * bt_ccp_call_control_server_register_bearer() before it can be unregistered. + * + * All TBS shall be unregistered before the GTBS can be unregistered with. + * + * @param bearer The bearer to unregister. + * + * @retval 0 Success + * @retval -EINVAL @p bearer is NULL + * @retval -EALREADY The bearer is not registered + * @retval -ENOEXEC The service failed to be unregistered + */ +int bt_ccp_call_control_server_unregister_bearer(struct bt_ccp_call_control_server_bearer *bearer); + +/** @} */ /* End of group bt_ccp_call_control_server */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_CCP_H_ */ diff --git a/samples/bluetooth/ccp_call_control_server/CMakeLists.txt b/samples/bluetooth/ccp_call_control_server/CMakeLists.txt new file mode 100644 index 0000000000000..67e198d981480 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ccp_call_control_server) + +target_sources(app PRIVATE + src/main.c +) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) diff --git a/samples/bluetooth/ccp_call_control_server/Kconfig.sysbuild b/samples/bluetooth/ccp_call_control_server/Kconfig.sysbuild new file mode 100644 index 0000000000000..f37b265ecbc27 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/Kconfig.sysbuild @@ -0,0 +1,15 @@ +# Copyright 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source "share/sysbuild/Kconfig" + +config NET_CORE_BOARD + string + default "nrf5340dk/nrf5340/cpunet" if "$(BOARD)" = "nrf5340dk" + default "nrf5340_audio_dk/nrf5340/cpunet" if "$(BOARD)" = "nrf5340_audio_dk" + default "nrf5340bsim/nrf5340/cpunet" if $(BOARD_TARGET_STRING) = "NRF5340BSIM_NRF5340_CPUAPP" + +config NET_CORE_IMAGE_HCI_IPC + bool "HCI IPC image on network core" + default y + depends on NET_CORE_BOARD != "" diff --git a/samples/bluetooth/ccp_call_control_server/README.rst b/samples/bluetooth/ccp_call_control_server/README.rst new file mode 100644 index 0000000000000..eab3bbbdc0ab9 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/README.rst @@ -0,0 +1,77 @@ +.. zephyr:code-sample:: bluetooth_ccp_call_control_server + :name: Call Control Profile (CCP) Call Control Server + :relevant-api: bluetooth bt_ccp bt_tbs + + CCP Call Control Server sample that registers one or more TBS bearers and advertises the + TBS UUID(s). + +Overview +******** + +Application demonstrating the CCP Call Control Server functionality. +Starts by advertising for CCP Call Control Clients to connect and set up calls. + +The profile works for both GAP Central and GAP Peripheral devices, but this sample only assumes the +GAP Peripheral role. + +This sample can be found under :zephyr_file:`samples/bluetooth/ccp_call_control_server` in the Zephyr tree. + +Check the :zephyr:code-sample-category:`bluetooth` samples for general information. + +Requirements +************ + +* BlueZ running on the host, or +* A board with Bluetooth Low Energy 5.2 support + +Building and Running +******************** + +When building targeting an nrf52 series board with the Zephyr Bluetooth Controller, +use ``-DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf`` to enable the required feature support. + +Building for an nrf5340dk +------------------------- + +You can build both the application core image and an appropriate controller image for the network +core with: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/ccp_call_control_server/ + :board: nrf5340dk/nrf5340/cpuapp + :goals: build + :west-args: --sysbuild + +If you prefer to only build the application core image, you can do so by doing instead: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/ccp_call_control_server/ + :board: nrf5340dk/nrf5340/cpuapp + :goals: build + +In that case you can pair this application core image with the +:zephyr:code-sample:`bluetooth_hci_ipc` sample +:zephyr_file:`samples/bluetooth/hci_ipc/nrf5340_cpunet_iso-bt_ll_sw_split.conf` configuration. + +Building for a simulated nrf5340bsim +------------------------------------ + +Similarly to how you would for real HW, you can do: + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/ccp_call_control_server/ + :board: nrf5340bsim/nrf5340/cpuapp + :goals: build + :west-args: --sysbuild + +Note this will produce a Linux executable in :file:`./build/zephyr/zephyr.exe`. +For more information, check :ref:`this board documentation `. + +Building for a simulated nrf52_bsim +----------------------------------- + +.. zephyr-app-commands:: + :zephyr-app: samples/bluetooth/ccp_call_control_server/ + :board: nrf52_bsim + :goals: build + :gen-args: -DOVERLAY_CONFIG=overlay-bt_ll_sw_split.conf diff --git a/samples/bluetooth/ccp_call_control_server/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf b/samples/bluetooth/ccp_call_control_server/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf new file mode 100644 index 0000000000000..f58eedb0453c5 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf @@ -0,0 +1,6 @@ +CONFIG_BT_BUF_EVT_RX_SIZE=255 +CONFIG_BT_BUF_ACL_RX_SIZE=255 +CONFIG_BT_BUF_ACL_TX_SIZE=251 +CONFIG_BT_BUF_CMD_TX_SIZE=255 + +CONFIG_BT_SEND_ECC_EMULATION=y diff --git a/samples/bluetooth/ccp_call_control_server/boards/nrf5340dk_nrf5340_cpuapp.conf b/samples/bluetooth/ccp_call_control_server/boards/nrf5340dk_nrf5340_cpuapp.conf new file mode 100644 index 0000000000000..f58eedb0453c5 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/boards/nrf5340dk_nrf5340_cpuapp.conf @@ -0,0 +1,6 @@ +CONFIG_BT_BUF_EVT_RX_SIZE=255 +CONFIG_BT_BUF_ACL_RX_SIZE=255 +CONFIG_BT_BUF_ACL_TX_SIZE=251 +CONFIG_BT_BUF_CMD_TX_SIZE=255 + +CONFIG_BT_SEND_ECC_EMULATION=y diff --git a/samples/bluetooth/ccp_call_control_server/overlay-bt_ll_sw_split.conf b/samples/bluetooth/ccp_call_control_server/overlay-bt_ll_sw_split.conf new file mode 100644 index 0000000000000..6c1e35227169a --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/overlay-bt_ll_sw_split.conf @@ -0,0 +1,5 @@ +# Zephyr Bluetooth Controller +CONFIG_BT_LL_SW_SPLIT=y + +# Zephyr Controller tested maximum advertising data that can be set in a single HCI command +CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=191 diff --git a/samples/bluetooth/ccp_call_control_server/prj.conf b/samples/bluetooth/ccp_call_control_server/prj.conf new file mode 100644 index 0000000000000..c05cd8850ab28 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/prj.conf @@ -0,0 +1,18 @@ +CONFIG_BT=y +CONFIG_LOG=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_AUDIO=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_SMP=y +CONFIG_BT_GATT_DYNAMIC_DB=y +CONFIG_BT_DEVICE_NAME="CCP Call Control Server" + +CONFIG_BT_SMP=y +CONFIG_BT_KEYS_OVERWRITE_OLDEST=y + +# CCP support +CONFIG_BT_CCP_CALL_CONTROL_SERVER=y +CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT=2 +CONFIG_BT_TBS=y +CONFIG_BT_TBS_BEARER_COUNT=1 +CONFIG_UTF8=y diff --git a/samples/bluetooth/ccp_call_control_server/sample.yaml b/samples/bluetooth/ccp_call_control_server/sample.yaml new file mode 100644 index 0000000000000..501c5b374210d --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/sample.yaml @@ -0,0 +1,30 @@ +sample: + description: Bluetooth Low Energy Call Control Profile Call Control Server sample + name: Bluetooth Low Energy Call Control Profile Call Control Server sample +tests: + sample.bluetooth.ccp_call_control_server: + harness: bluetooth + platform_allow: + - qemu_cortex_m3 + - qemu_x86 + - nrf5340dk/nrf5340/cpuapp + - nrf5340bsim/nrf5340/cpuapp + integration_platforms: + - qemu_x86 + - nrf5340dk/nrf5340/cpuapp + tags: bluetooth + sysbuild: true + sample.bluetooth.ccp_call_control_server.bt_ll_sw_split: + harness: bluetooth + platform_allow: + - nrf52_bsim + - nrf52833dk/nrf52833 + - nrf52840dk/nrf52840 + - nrf52840dongle/nrf52840 + integration_platforms: + - nrf52_bsim + - nrf52833dk/nrf52833 + - nrf52840dk/nrf52840 + - nrf52840dongle/nrf52840 + extra_args: OVERLAY_CONFIG=overlay-bt_ll_sw_split.conf + tags: bluetooth diff --git a/samples/bluetooth/ccp_call_control_server/src/main.c b/samples/bluetooth/ccp_call_control_server/src/main.c new file mode 100644 index 0000000000000..df0c37b811f37 --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/src/main.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ccp_call_control_server, CONFIG_LOG_DEFAULT_LEVEL); + +#define SEM_TIMEOUT K_SECONDS(5) + +static const struct bt_data ad[] = { + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID16_SOME, BT_UUID_16_ENCODE(BT_UUID_GTBS_VAL)), + BT_DATA_BYTES(BT_DATA_SVC_DATA16, BT_UUID_16_ENCODE(BT_UUID_GTBS_VAL)), + IF_ENABLED(CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT > 1, + (BT_DATA_BYTES(BT_DATA_UUID16_SOME, BT_UUID_16_ENCODE(BT_UUID_TBS_VAL)), + BT_DATA_BYTES(BT_DATA_SVC_DATA16, BT_UUID_16_ENCODE(BT_UUID_TBS_VAL))))}; + +static struct bt_le_ext_adv *adv; +static struct bt_conn *peer_conn; +static struct bt_ccp_call_control_server_bearer + *bearers[CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT]; + +static K_SEM_DEFINE(sem_state_change, 0, 1); + +static void connected_cb(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)); + LOG_INF("Connected: %s", addr); + + peer_conn = bt_conn_ref(conn); + k_sem_give(&sem_state_change); +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + if (conn != peer_conn) { + return; + } + + (void)bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + LOG_INF("Disconnected: %s (reason 0x%02x)", addr, reason); + + bt_conn_unref(peer_conn); + peer_conn = NULL; + k_sem_give(&sem_state_change); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected_cb, + .disconnected = disconnected_cb, +}; + +static int advertise(void) +{ + int err; + + err = bt_le_ext_adv_create(BT_LE_EXT_ADV_CONN, NULL, &adv); + if (err) { + LOG_ERR("Failed to create advertising set: %d", err); + + return err; + } + + err = bt_le_ext_adv_set_data(adv, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + LOG_ERR("Failed to set advertising data: %d", err); + + return err; + } + + err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + if (err) { + LOG_ERR("Failed to start advertising set: %d", err); + + return err; + } + + LOG_INF("Advertising successfully started"); + + /* Wait for connection*/ + err = k_sem_take(&sem_state_change, K_FOREVER); + if (err != 0) { + LOG_ERR("Failed to take sem_state_change: err %d", err); + + return err; + } + + return 0; +} + +static int reset_ccp_call_control_server(void) +{ + int err; + + LOG_INF("Resetting"); + + if (peer_conn != NULL) { + err = bt_conn_disconnect(peer_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err != 0) { + return err; + } + + err = k_sem_take(&sem_state_change, K_FOREVER); + if (err != 0) { + LOG_ERR("Failed to take sem_state_change: %d", err); + return err; + } + } + + if (adv != NULL) { + err = bt_le_ext_adv_stop(adv); + if (err != 0) { + LOG_ERR("Failed to stop advertiser: %d", err); + return err; + } + + err = bt_le_ext_adv_delete(adv); + if (err != 0) { + LOG_ERR("Failed to delete advertiser: %d", err); + return err; + } + + adv = NULL; + } + + k_sem_reset(&sem_state_change); + + return 0; +} + +static int init_ccp_call_control_server(void) +{ + const struct bt_tbs_register_param gtbs_param = { + .provider_name = "Generic TBS", + .uci = "un000", + .uri_schemes_supported = "tel,skype", + .gtbs = true, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = CONFIG_BT_TBS_SUPPORTED_FEATURES, + }; + int err; + + err = bt_enable(NULL); + if (err != 0) { + LOG_ERR("Bluetooth enable failed (err %d)", err); + + return err; + } + + LOG_DBG("Bluetooth initialized"); + + err = bt_ccp_call_control_server_register_bearer(>bs_param, &bearers[0]); + if (err < 0) { + LOG_ERR("Failed to register GTBS (err %d)", err); + + return err; + } + + LOG_INF("Registered GTBS bearer"); + + for (int i = 1; i < CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT; i++) { + char prov_name[22]; /* Enough to store "Telephone Bearer #255" */ + const struct bt_tbs_register_param tbs_param = { + .provider_name = prov_name, + .uci = "un000", + .uri_schemes_supported = "tel,skype", + .gtbs = false, + .authorization_required = false, + /* Set different technologies per bearer */ + .technology = (i % BT_TBS_TECHNOLOGY_WCDMA) + 1, + .supported_features = CONFIG_BT_TBS_SUPPORTED_FEATURES, + }; + + snprintf(prov_name, sizeof(prov_name), "Telephone Bearer #%d", i); + + err = bt_ccp_call_control_server_register_bearer(&tbs_param, &bearers[i]); + if (err < 0) { + LOG_ERR("Failed to register bearer[%d]: %d", i, err); + + return err; + } + + LOG_INF("Registered bearer[%d]", i); + } + + return 0; +} + +int main(void) +{ + int err; + + err = init_ccp_call_control_server(); + if (err != 0) { + return 0; + } + + LOG_INF("CCP Call Control Server initialized"); + + while (true) { + err = reset_ccp_call_control_server(); + if (err != 0) { + LOG_ERR("Failed to reset"); + + break; + } + + /* Start advertising as a CCP Call Control Server, which includes setting the + * required advertising data based on the roles we support. + */ + err = advertise(); + if (err != 0) { + continue; + } + + /* After advertising we expect CCP Call Control Clients to connect to us and + * eventually disconnect again. As a CCP Call Control Server we just react to their + * requests and not do anything else. + */ + + /* Reset if disconnected */ + err = k_sem_take(&sem_state_change, K_FOREVER); + if (err != 0) { + LOG_ERR("Failed to take sem_state_change: err %d", err); + + break; + } + } + + return 0; +} diff --git a/samples/bluetooth/ccp_call_control_server/sysbuild.cmake b/samples/bluetooth/ccp_call_control_server/sysbuild.cmake new file mode 100644 index 0000000000000..2523aac8ea76f --- /dev/null +++ b/samples/bluetooth/ccp_call_control_server/sysbuild.cmake @@ -0,0 +1,24 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +if(SB_CONFIG_NET_CORE_IMAGE_HCI_IPC) + # For builds in the nrf5340, we build the netcore image with the controller + + set(NET_APP hci_ipc) + set(NET_APP_SRC_DIR ${ZEPHYR_BASE}/samples/bluetooth/${NET_APP}) + + ExternalZephyrProject_Add( + APPLICATION ${NET_APP} + SOURCE_DIR ${NET_APP_SRC_DIR} + BOARD ${SB_CONFIG_NET_CORE_BOARD} + ) + + set(${NET_APP}_CONF_FILE + ${NET_APP_SRC_DIR}/nrf5340_cpunet_iso-bt_ll_sw_split.conf + CACHE INTERNAL "" + ) + + native_simulator_set_child_images(${DEFAULT_IMAGE} ${NET_APP}) +endif() + +native_simulator_set_final_executable(${DEFAULT_IMAGE}) diff --git a/samples/bluetooth/tmap_central/CMakeLists.txt b/samples/bluetooth/tmap_central/CMakeLists.txt index dd1bca04e1302..7a122c1157c89 100644 --- a/samples/bluetooth/tmap_central/CMakeLists.txt +++ b/samples/bluetooth/tmap_central/CMakeLists.txt @@ -8,7 +8,7 @@ target_sources(app PRIVATE src/main.c src/mcp_server.c src/vcp_vol_ctlr.c - src/ccp_server.c + src/ccp_call_control_server.c src/cap_initiator.c ) diff --git a/samples/bluetooth/tmap_central/prj.conf b/samples/bluetooth/tmap_central/prj.conf index 0718245e20106..2d2b50dc1173d 100644 --- a/samples/bluetooth/tmap_central/prj.conf +++ b/samples/bluetooth/tmap_central/prj.conf @@ -35,6 +35,7 @@ CONFIG_MCTL_LOCAL_PLAYER_CONTROL=y CONFIG_MCTL=y # CCP support +CONFIG_BT_CCP_CALL_CONTROL_SERVER=y CONFIG_BT_TBS=y CONFIG_BT_TBS_SUPPORTED_FEATURES=3 diff --git a/samples/bluetooth/tmap_central/src/ccp_server.c b/samples/bluetooth/tmap_central/src/ccp_call_control_server.c similarity index 96% rename from samples/bluetooth/tmap_central/src/ccp_server.c rename to samples/bluetooth/tmap_central/src/ccp_call_control_server.c index 7f21563cfe0d1..9ddbcfff01a6c 100644 --- a/samples/bluetooth/tmap_central/src/ccp_server.c +++ b/samples/bluetooth/tmap_central/src/ccp_call_control_server.c @@ -44,7 +44,7 @@ static struct bt_tbs_cb tbs_cbs = { .authorize = NULL, }; -int ccp_server_init(void) +int ccp_call_control_server_init(void) { int err; diff --git a/samples/bluetooth/tmap_central/src/main.c b/samples/bluetooth/tmap_central/src/main.c index 98edc30bf9dc7..4c47e232bc08b 100644 --- a/samples/bluetooth/tmap_central/src/main.c +++ b/samples/bluetooth/tmap_central/src/main.c @@ -306,8 +306,8 @@ int main(void) } printk("MCP initialized\n"); - /* Initialize CCP Server */ - err = ccp_server_init(); + /* Initialize CCP Call Control Server */ + err = ccp_call_control_server_init(); if (err != 0) { return err; } diff --git a/samples/bluetooth/tmap_central/src/tmap_central.h b/samples/bluetooth/tmap_central/src/tmap_central.h index 1b2277a3b5be6..6792c03351163 100644 --- a/samples/bluetooth/tmap_central/src/tmap_central.h +++ b/samples/bluetooth/tmap_central/src/tmap_central.h @@ -18,11 +18,11 @@ int mcp_server_init(void); /** - * @brief Initialize the CCP Server role + * @brief Initialize the CCP Call Control Server role * * @return 0 if success, errno on failure. */ -int ccp_server_init(void); +int ccp_call_control_server_init(void); /** * @brief Initialize the VCP Volume Controller role diff --git a/samples/bluetooth/tmap_peripheral/prj.conf b/samples/bluetooth/tmap_peripheral/prj.conf index ebe30c7548639..b073c27e2fa75 100644 --- a/samples/bluetooth/tmap_peripheral/prj.conf +++ b/samples/bluetooth/tmap_peripheral/prj.conf @@ -43,7 +43,7 @@ CONFIG_BT_PAC_SNK_LOC=y # Source PAC Location Support CONFIG_BT_PAC_SRC_LOC=y -# CCP Client Support +# CCP Call Control Client Support CONFIG_BT_TBS_CLIENT_GTBS=y CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL=y CONFIG_BT_TBS_CLIENT_TERMINATE_CALL=y diff --git a/subsys/bluetooth/audio/CMakeLists.txt b/subsys/bluetooth/audio/CMakeLists.txt index ef6fc16640b22..86f507746be2d 100644 --- a/subsys/bluetooth/audio/CMakeLists.txt +++ b/subsys/bluetooth/audio/CMakeLists.txt @@ -58,6 +58,7 @@ zephyr_library_sources_ifdef(CONFIG_BT_BAP_BROADCAST_SOURCE bap_broadcast_source zephyr_library_sources_ifdef(CONFIG_BT_BAP_BROADCAST_SINK bap_broadcast_sink.c) zephyr_library_sources_ifdef(CONFIG_BT_BAP_SCAN_DELEGATOR bap_scan_delegator.c) zephyr_library_sources_ifdef(CONFIG_BT_BAP_BROADCAST_ASSISTANT bap_broadcast_assistant.c) +zephyr_library_sources_ifdef(CONFIG_BT_CCP_CALL_CONTROL_SERVER ccp_call_control_server.c) zephyr_library_sources_ifdef(CONFIG_BT_HAS has.c) zephyr_library_sources_ifdef(CONFIG_BT_HAS_CLIENT has_client.c) zephyr_library_sources_ifdef(CONFIG_BT_CAP cap_stream.c) diff --git a/subsys/bluetooth/audio/Kconfig b/subsys/bluetooth/audio/Kconfig index f56c3fe5236ad..bc3335aaf5dff 100644 --- a/subsys/bluetooth/audio/Kconfig +++ b/subsys/bluetooth/audio/Kconfig @@ -34,6 +34,7 @@ config BT_AUDIO_NOTIFY_RETRY_DELAY available. rsource "Kconfig.bap" +rsource "Kconfig.ccp" rsource "Kconfig.vocs" rsource "Kconfig.aics" rsource "Kconfig.vcp" diff --git a/subsys/bluetooth/audio/Kconfig.ccp b/subsys/bluetooth/audio/Kconfig.ccp new file mode 100644 index 0000000000000..f43557e37a869 --- /dev/null +++ b/subsys/bluetooth/audio/Kconfig.ccp @@ -0,0 +1,34 @@ +# Bluetooth Audio - Call Control Profile (CCP) configuration options +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +if BT_AUDIO + +config BT_CCP_CALL_CONTROL_SERVER + bool "Call Control Profile Call Control Server Support" + depends on BT_EXT_ADV + depends on BT_TBS + depends on BT_BONDABLE + help + This option enables support for the Call Control Profile Call Control Server which uses + the Telephone Bearer Service (TBS) to hold and control calls on a device. + +if BT_CCP_CALL_CONTROL_SERVER + +config BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT + int "Telephone bearer count" + default 1 + range 1 $(UINT8_MAX) + help + The number of supported telephone bearers on the CCP Call Control Server + +module = BT_CCP_CALL_CONTROL_SERVER +module-str = "Call Control Profile Call Control Server" +source "subsys/logging/Kconfig.template.log_config" + +endif # BT_CCP_CALL_CONTROL_SERVER + +endif # BT_AUDIO diff --git a/subsys/bluetooth/audio/ccp_call_control_server.c b/subsys/bluetooth/audio/ccp_call_control_server.c new file mode 100644 index 0000000000000..a51354f4ce42d --- /dev/null +++ b/subsys/bluetooth/audio/ccp_call_control_server.c @@ -0,0 +1,106 @@ +/* Bluetooth CCP - Call Control Profile Call Control Server + * + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_ccp_call_control_server, CONFIG_BT_CCP_CALL_CONTROL_SERVER_LOG_LEVEL); + +/* A service instance can either be a GTBS or a TBS instance */ +struct bt_ccp_call_control_server_bearer { + uint8_t tbs_index; + bool registered; +}; + +static struct bt_ccp_call_control_server_bearer + bearers[CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT]; + +static struct bt_ccp_call_control_server_bearer *get_free_bearer(void) +{ + + for (size_t i = 0; i < ARRAY_SIZE(bearers); i++) { + if (!bearers[i].registered) { + return &bearers[i]; + } + } + + return NULL; +} + +int bt_ccp_call_control_server_register_bearer(const struct bt_tbs_register_param *param, + struct bt_ccp_call_control_server_bearer **bearer) +{ + struct bt_ccp_call_control_server_bearer *free_bearer; + int ret; + + CHECKIF(bearer == NULL) { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + free_bearer = get_free_bearer(); + if (free_bearer == NULL) { + return -ENOMEM; + } + + ret = bt_tbs_register_bearer(param); + if (ret < 0) { + LOG_DBG("Failed to register TBS bearer: %d", ret); + + /* Return known errors */ + if (ret == -EINVAL || ret == -EALREADY || ret == -EAGAIN || ret == -ENOMEM) { + return ret; + } + + return -ENOEXEC; + } + + free_bearer->registered = true; + free_bearer->tbs_index = (uint8_t)ret; + *bearer = free_bearer; + + return 0; +} + +int bt_ccp_call_control_server_unregister_bearer(struct bt_ccp_call_control_server_bearer *bearer) +{ + int err; + + CHECKIF(bearer == NULL) { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (!bearer->registered) { + LOG_DBG("Bearer %p already unregistered", bearer); + + return -EALREADY; + } + + err = bt_tbs_unregister_bearer(bearer->tbs_index); + if (err != 0) { + /* Return known errors */ + if (err == -EINVAL || err == -EALREADY) { + return err; + } + + return -ENOEXEC; + } + + bearer->registered = false; + + return 0; +} diff --git a/subsys/bluetooth/audio/shell/CMakeLists.txt b/subsys/bluetooth/audio/shell/CMakeLists.txt index d44f4e1841777..5fe6a71b61509 100644 --- a/subsys/bluetooth/audio/shell/CMakeLists.txt +++ b/subsys/bluetooth/audio/shell/CMakeLists.txt @@ -3,6 +3,10 @@ zephyr_library() zephyr_library_link_libraries(subsys__bluetooth) +zephyr_library_sources_ifdef( + CONFIG_BT_CCP_CALL_CONTROL_SERVER + ccp_call_control_server.c + ) zephyr_library_sources_ifdef( CONFIG_BT_VCP_VOL_REND vcp_vol_rend.c diff --git a/subsys/bluetooth/audio/shell/ccp_call_control_server.c b/subsys/bluetooth/audio/shell/ccp_call_control_server.c new file mode 100644 index 0000000000000..0d76cc5877a0a --- /dev/null +++ b/subsys/bluetooth/audio/shell/ccp_call_control_server.c @@ -0,0 +1,100 @@ +/** @file + * @brief Bluetooth Call Control Profile Call Control Server shell + */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include +#include +#include +#include + +static struct bt_ccp_call_control_server_bearer + *bearers[CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT]; + +static int cmd_ccp_call_control_server_init(const struct shell *sh, size_t argc, char *argv[]) +{ + static bool registered; + + if (registered) { + shell_info(sh, "Already initialized"); + + return -ENOEXEC; + } + + const struct bt_tbs_register_param gtbs_param = { + .provider_name = "Generic TBS", + .uci = "un000", + .uri_schemes_supported = "tel,skype", + .gtbs = true, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = CONFIG_BT_TBS_SUPPORTED_FEATURES, + }; + int err; + + err = bt_ccp_call_control_server_register_bearer(>bs_param, &bearers[0]); + if (err != 0) { + shell_error(sh, "Failed to register GTBS bearer: %d", err); + + return -ENOEXEC; + } + + shell_info(sh, "Registered GTBS bearer"); + + for (int i = 1; i < CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT; i++) { + char prov_name[22]; /* Enough to store "Telephone Bearer #255" */ + const struct bt_tbs_register_param tbs_param = { + .provider_name = prov_name, + .uci = "un000", + .uri_schemes_supported = "tel,skype", + .gtbs = false, + .authorization_required = false, + /* Set different technologies per bearer */ + .technology = (i % BT_TBS_TECHNOLOGY_WCDMA) + 1, + .supported_features = CONFIG_BT_TBS_SUPPORTED_FEATURES, + }; + + snprintf(prov_name, sizeof(prov_name), "Telephone Bearer #%d", i); + + err = bt_ccp_call_control_server_register_bearer(&tbs_param, &bearers[i]); + if (err != 0) { + shell_error(sh, "Failed to register bearer[%d]: %d", i, err); + + return -ENOEXEC; + } + + shell_info(sh, "Registered bearer[%d]", i); + } + + registered = true; + + return 0; +} + +static int cmd_ccp_call_control_server(const struct shell *sh, size_t argc, char **argv) +{ + if (argc > 1) { + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + } else { + shell_error(sh, "%s Missing subcommand", argv[0]); + } + + return -ENOEXEC; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(ccp_call_control_server_cmds, + SHELL_CMD_ARG(init, NULL, "Initialize CCP Call Control Server", + cmd_ccp_call_control_server_init, 1, 0), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_ARG_REGISTER(ccp_call_control_server, &ccp_call_control_server_cmds, + "Bluetooth CCP Call Control Server shell commands", + cmd_ccp_call_control_server, 1, 1); diff --git a/tests/bluetooth/audio/ccp_call_control_server/CMakeLists.txt b/tests/bluetooth/audio/ccp_call_control_server/CMakeLists.txt new file mode 100644 index 0000000000000..40b476c4d66a3 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_server/CMakeLists.txt @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +project(bluetooth_ccp) +find_package(Zephyr COMPONENTS unittest HINTS $ENV{ZEPHYR_BASE}) + +add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/audio/ccp_call_control_server/uut uut) + +target_link_libraries(testbinary PRIVATE uut) + +target_include_directories(testbinary PRIVATE include) + +target_sources(testbinary + PRIVATE + src/main.c +) diff --git a/tests/bluetooth/audio/ccp_call_control_server/prj.conf b/tests/bluetooth/audio/ccp_call_control_server/prj.conf new file mode 100644 index 0000000000000..e7d7406bf5f6a --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_server/prj.conf @@ -0,0 +1,21 @@ +CONFIG_ZTEST=y + +CONFIG_BT=y +CONFIG_BT_SMP=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_GATT_DYNAMIC_DB=y +CONFIG_BT_AUDIO=y +CONFIG_BT_CCP_CALL_CONTROL_SERVER=y +CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT=2 +CONFIG_BT_TBS=y +CONFIG_BT_TBS_BEARER_COUNT=1 +CONFIG_UTF8=y + +CONFIG_ASSERT=y +CONFIG_ASSERT_LEVEL=2 +CONFIG_ASSERT_VERBOSE=y + +CONFIG_LOG=y +CONFIG_BT_CCP_CALL_CONTROL_SERVER_LOG_LEVEL_DBG=y +CONFIG_BT_TBS_LOG_LEVEL_DBG=y diff --git a/tests/bluetooth/audio/ccp_call_control_server/src/main.c b/tests/bluetooth/audio/ccp_call_control_server/src/main.c new file mode 100644 index 0000000000000..4d45690885f70 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_server/src/main.c @@ -0,0 +1,264 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +DEFINE_FFF_GLOBALS; + +struct ccp_call_control_server_test_suite_fixture { + /** Need 1 additional bearer than the max to trigger some corner cases */ + struct bt_ccp_call_control_server_bearer + *bearers[CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT + 1]; +}; + +static void *ccp_call_control_server_test_suite_setup(void) +{ + struct ccp_call_control_server_test_suite_fixture *fixture; + + fixture = malloc(sizeof(*fixture)); + zassert_not_null(fixture); + + return fixture; +} + +static void ccp_call_control_server_test_suite_before(void *f) +{ + memset(f, 0, sizeof(struct ccp_call_control_server_test_suite_fixture)); +} + +static void ccp_call_control_server_test_suite_after(void *f) +{ + struct ccp_call_control_server_test_suite_fixture *fixture = f; + + /* We unregister from largest to lowest index, as GTBS shall be unregistered last and is + * always at index 0 + */ + for (size_t i = ARRAY_SIZE(fixture->bearers); i > 0; i--) { + /* Since size_t cannot be negative, we cannot use the regular i >= 0 when counting + * downwards as that will always be true, so we use an additional index variable + */ + const size_t index_to_unreg = i - 1; + + if (fixture->bearers[index_to_unreg] != NULL) { + bt_ccp_call_control_server_unregister_bearer( + fixture->bearers[index_to_unreg]); + } + + fixture->bearers[index_to_unreg] = NULL; + } +} + +static void ccp_call_control_server_test_suite_teardown(void *f) +{ + free(f); +} + +ZTEST_SUITE(ccp_call_control_server_test_suite, NULL, ccp_call_control_server_test_suite_setup, + ccp_call_control_server_test_suite_before, ccp_call_control_server_test_suite_after, + ccp_call_control_server_test_suite_teardown); + +static void register_default_bearer(struct ccp_call_control_server_test_suite_fixture *fixture) +{ + const struct bt_tbs_register_param register_param = { + .provider_name = "test", + .uci = "un999", + .uri_schemes_supported = "tel", + .gtbs = true, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = 0, + }; + int err; + + err = bt_ccp_call_control_server_register_bearer(®ister_param, &fixture->bearers[0]); + zassert_equal(err, 0, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, test_ccp_call_control_server_register_bearer) +{ + register_default_bearer(fixture); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_ccp_call_control_server_register_multiple_bearers) +{ + if (CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT == 1) { + ztest_test_skip(); + } + + register_default_bearer(fixture); + + for (int i = 1; i < CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT; i++) { + struct bt_tbs_register_param register_param = { + .provider_name = "test", + .uci = "un999", + .uri_schemes_supported = "tel", + .gtbs = false, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = 0, + }; + int err; + + err = bt_ccp_call_control_server_register_bearer(®ister_param, + &fixture->bearers[i]); + zassert_equal(err, 0, "Unexpected return value %d", err); + } +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_ccp_call_control_server_register_bearer_inval_null_param) +{ + int err; + + err = bt_ccp_call_control_server_register_bearer(NULL, &fixture->bearers[0]); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_ccp_call_control_server_register_bearer_inval_null_bearer) +{ + const struct bt_tbs_register_param register_param = { + .provider_name = "test", + .uci = "un999", + .uri_schemes_supported = "tel", + .gtbs = true, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = 0, + }; + int err; + + err = bt_ccp_call_control_server_register_bearer(®ister_param, NULL); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_ccp_call_control_server_register_bearer_inval_no_gtbs) +{ + const struct bt_tbs_register_param register_param = { + .provider_name = "test", + .uci = "un999", + .uri_schemes_supported = "tel", + .gtbs = false, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = 0, + }; + int err; + + err = bt_ccp_call_control_server_register_bearer(®ister_param, &fixture->bearers[0]); + zassert_equal(err, -EAGAIN, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_ccp_call_control_server_register_bearer_inval_double_gtbs) +{ + const struct bt_tbs_register_param register_param = { + .provider_name = "test", + .uci = "un999", + .uri_schemes_supported = "tel", + .gtbs = true, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = 0, + }; + int err; + + if (CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT == 1) { + ztest_test_skip(); + } + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_register_bearer(®ister_param, &fixture->bearers[1]); + zassert_equal(err, -EALREADY, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_ccp_call_control_server_register_bearer_inval_cnt) +{ + const struct bt_tbs_register_param register_param = { + .provider_name = "test", + .uci = "un999", + .uri_schemes_supported = "tel", + .gtbs = false, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = 0, + }; + int err; + + if (CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT == 1) { + ztest_test_skip(); + } + + register_default_bearer(fixture); + + for (int i = 1; i < CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT; i++) { + + err = bt_ccp_call_control_server_register_bearer(®ister_param, + &fixture->bearers[i]); + zassert_equal(err, 0, "Unexpected return value %d", err); + } + + err = bt_ccp_call_control_server_register_bearer( + ®ister_param, &fixture->bearers[CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT]); + zassert_equal(err, -ENOMEM, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, test_ccp_call_control_server_unregister_bearer) +{ + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_unregister_bearer(fixture->bearers[0]); + zassert_equal(err, 0, "Unexpected return value %d", err); +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_ccp_call_control_server_unregister_bearer_inval_double_unregister) +{ + int err; + + register_default_bearer(fixture); + + err = bt_ccp_call_control_server_unregister_bearer(fixture->bearers[0]); + zassert_equal(err, 0, "Unexpected return value %d", err); + + err = bt_ccp_call_control_server_unregister_bearer(fixture->bearers[0]); + zassert_equal(err, -EALREADY, "Unexpected return value %d", err); + + fixture->bearers[0] = NULL; +} + +static ZTEST_F(ccp_call_control_server_test_suite, + test_ccp_call_control_server_unregister_bearer_inval_null_bearer) +{ + int err; + + err = bt_ccp_call_control_server_unregister_bearer(NULL); + zassert_equal(err, -EINVAL, "Unexpected return value %d", err); +} diff --git a/tests/bluetooth/audio/ccp_call_control_server/testcase.yaml b/tests/bluetooth/audio/ccp_call_control_server/testcase.yaml new file mode 100644 index 0000000000000..1a75b740147f2 --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_server/testcase.yaml @@ -0,0 +1,7 @@ +common: + tags: + - bluetooth + - bluetooth_audio +tests: + bluetooth.audio.ccp_call_control_server.test: + type: unit diff --git a/tests/bluetooth/audio/ccp_call_control_server/uut/CMakeLists.txt b/tests/bluetooth/audio/ccp_call_control_server/uut/CMakeLists.txt new file mode 100644 index 0000000000000..6dd650947733b --- /dev/null +++ b/tests/bluetooth/audio/ccp_call_control_server/uut/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# +# CMakeLists.txt file for creating of uut library. +# + +add_library(uut STATIC + ${ZEPHYR_BASE}/subsys/bluetooth/audio/audio.c + ${ZEPHYR_BASE}/subsys/bluetooth/audio/ccid.c + ${ZEPHYR_BASE}/subsys/bluetooth/audio/ccp_call_control_server.c + ${ZEPHYR_BASE}/subsys/bluetooth/audio/tbs.c + ${ZEPHYR_BASE}/lib/net_buf/buf_simple.c + ${ZEPHYR_BASE}/lib/utils/utf8.c +) + +add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/audio/mocks mocks) + +target_link_libraries(uut PUBLIC test_interface mocks) + +target_compile_options(uut PRIVATE -std=c11 -include ztest.h) diff --git a/tests/bluetooth/shell/audio.conf b/tests/bluetooth/shell/audio.conf index 5e4e57ff561f2..fbdd2f912d602 100644 --- a/tests/bluetooth/shell/audio.conf +++ b/tests/bluetooth/shell/audio.conf @@ -130,7 +130,9 @@ CONFIG_BT_MPL_MAX_OBJ_SIZE=600 CONFIG_BT_MPL_ICON_BITMAP_SIZE=321 CONFIG_BT_MPL_TRACK_MAX_SIZE=50 -# Telephone bearer service +# Call Control +CONFIG_BT_CCP_CALL_CONTROL_SERVER=y +CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT=2 CONFIG_BT_TBS=y CONFIG_BT_TBS_BEARER_COUNT=1 CONFIG_BT_TBS_SUPPORTED_FEATURES=3 diff --git a/tests/bluetooth/shell/testcase.yaml b/tests/bluetooth/shell/testcase.yaml index a7e4c13aeeee8..b7e88cdd8d9b2 100644 --- a/tests/bluetooth/shell/testcase.yaml +++ b/tests/bluetooth/shell/testcase.yaml @@ -312,6 +312,7 @@ tests: extra_args: CONF_FILE="audio.conf" build_only: true extra_configs: + - CONFIG_BT_CCP_CALL_CONTROL_SERVER=n - CONFIG_BT_TBS=n tags: bluetooth bluetooth.shell.audio.only_gtbs: @@ -419,3 +420,9 @@ tests: - nrf52_bsim integration_platforms: - nrf52_bsim + bluetooth.shell.audio.no_ccp_call_control_server: + extra_args: CONF_FILE="audio.conf" + build_only: true + extra_configs: + - CONFIG_BT_CCP_CALL_CONTROL_SERVER=n + tags: bluetooth diff --git a/tests/bluetooth/tester/overlay-le-audio.conf b/tests/bluetooth/tester/overlay-le-audio.conf index 2f41aa14ebb78..ed196ca6e5fa1 100644 --- a/tests/bluetooth/tester/overlay-le-audio.conf +++ b/tests/bluetooth/tester/overlay-le-audio.conf @@ -127,6 +127,7 @@ CONFIG_BT_CSIP_SET_COORDINATOR=y CONFIG_BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES=3 # CCP +CONFIG_BT_CCP_CALL_CONTROL_SERVER=y CONFIG_BT_ATT_TX_COUNT=12 CONFIG_BT_TBS_CLIENT_GTBS=y CONFIG_BT_TBS_CLIENT_TBS=y diff --git a/tests/bsim/bluetooth/audio/prj.conf b/tests/bsim/bluetooth/audio/prj.conf index dcc7fd5f40f5b..fad001802e481 100644 --- a/tests/bsim/bluetooth/audio/prj.conf +++ b/tests/bsim/bluetooth/audio/prj.conf @@ -89,7 +89,8 @@ CONFIG_BT_CSIP_SET_MEMBER_NOTIFIABLE=y CONFIG_BT_CSIP_SET_COORDINATOR=y CONFIG_BT_CSIP_SET_COORDINATOR_TEST_SAMPLE_DATA=y -# Telephone bearer service +# Call control +CONFIG_BT_CCP_CALL_CONTROL_SERVER=y CONFIG_BT_TBS=y CONFIG_BT_TBS_BEARER_COUNT=1 CONFIG_BT_TBS_CLIENT_TBS=y diff --git a/tests/bsim/bluetooth/audio/src/ccp_call_control_server_test.c b/tests/bsim/bluetooth/audio/src/ccp_call_control_server_test.c new file mode 100644 index 0000000000000..dfc6ef579531e --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/ccp_call_control_server_test.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bstests.h" +#include "common.h" + +#ifdef CONFIG_BT_CCP_CALL_CONTROL_SERVER +LOG_MODULE_REGISTER(ccp_call_control_server, CONFIG_LOG_DEFAULT_LEVEL); + +extern enum bst_result_t bst_result; + +static struct bt_ccp_call_control_server_bearer + *bearers[CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT]; + +CREATE_FLAG(is_connected); +static void connected(struct bt_conn *conn, uint8_t err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (err != 0) { + FAIL("Failed to connect to %s (%u)\n", addr, err); + return; + } + + LOG_DBG("Connected to %s", addr); + + default_conn = bt_conn_ref(conn); + SET_FLAG(is_connected); +} + +static void init(void) +{ + static struct bt_conn_cb conn_callbacks = { + .connected = connected, + .disconnected = disconnected, + }; + + const struct bt_tbs_register_param gtbs_param = { + .provider_name = "Generic TBS", + .uci = "un000", + .uri_schemes_supported = "tel,skype", + .gtbs = true, + .authorization_required = false, + .technology = BT_TBS_TECHNOLOGY_3G, + .supported_features = CONFIG_BT_TBS_SUPPORTED_FEATURES, + }; + int err; + + err = bt_enable(NULL); + if (err != 0) { + FAIL("Bluetooth enable failed (err %d)\n", err); + + return; + } + + LOG_DBG("Bluetooth initialized"); + + err = bt_conn_cb_register(&conn_callbacks); + if (err != 0) { + FAIL("Failed to register conn CBs (err %d)\n", err); + + return; + } + + err = bt_le_scan_cb_register(&common_scan_cb); + if (err != 0) { + FAIL("Failed to register scan CBs (err %d)\n", err); + + return; + } + + err = bt_ccp_call_control_server_register_bearer(>bs_param, &bearers[0]); + if (err < 0) { + FAIL("Failed to register GTBS (err %d)\n", err); + + return; + } + + LOG_INF("Registered GTBS bearer"); + + for (int i = 1; i < CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT; i++) { + char prov_name[22]; /* Enough to store "Telephone Bearer #255" */ + const struct bt_tbs_register_param tbs_param = { + .provider_name = prov_name, + .uci = "un000", + .uri_schemes_supported = "tel,skype", + .gtbs = false, + .authorization_required = false, + /* Set different technologies per bearer */ + .technology = (i % BT_TBS_TECHNOLOGY_WCDMA) + 1, + .supported_features = CONFIG_BT_TBS_SUPPORTED_FEATURES, + }; + + snprintf(prov_name, sizeof(prov_name), "Telephone Bearer #%d", i); + + err = bt_ccp_call_control_server_register_bearer(&tbs_param, &bearers[i]); + if (err < 0) { + FAIL("Failed to register bearer[%d]: %d\n", i, err); + + return; + } + + LOG_INF("Registered bearer[%d]", i); + } +} + +static void unregister_bearers(void) +{ + for (int i = 0; i < CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT; i++) { + if (bearers[i] != NULL) { + const int err = bt_ccp_call_control_server_unregister_bearer(bearers[i]); + + if (err < 0) { + FAIL("Failed to unregister bearer[%d]: %d\n", i, err); + + return; + } + + LOG_DBG("Unregistered bearer[%d]", i); + + bearers[i] = NULL; + } + } +} + +static void test_main(void) +{ + init(); + + unregister_bearers(); + + PASS("CCP Call Control Server Passed\n"); +} + +static const struct bst_test_instance test_ccp_call_control_server[] = { + { + .test_id = "ccp_call_control_server", + .test_pre_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_main, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_ccp_call_control_server_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_ccp_call_control_server); +} +#else +struct bst_test_list *test_ccp_call_control_server_install(struct bst_test_list *tests) +{ + return tests; +} + +#endif /* CONFIG_BT_TBS */ diff --git a/tests/bsim/bluetooth/audio/src/main.c b/tests/bsim/bluetooth/audio/src/main.c index 4490087eacc9c..7fc13176bae9c 100644 --- a/tests/bsim/bluetooth/audio/src/main.c +++ b/tests/bsim/bluetooth/audio/src/main.c @@ -44,6 +44,7 @@ extern struct bst_test_list *test_csip_notify_client_install(struct bst_test_lis extern struct bst_test_list *test_csip_notify_server_install(struct bst_test_list *tests); extern struct bst_test_list *test_gmap_ugg_install(struct bst_test_list *tests); extern struct bst_test_list *test_gmap_ugt_install(struct bst_test_list *tests); +extern struct bst_test_list *test_ccp_call_control_server_install(struct bst_test_list *tests); bst_test_install_t test_installers[] = { test_vcp_install, @@ -82,6 +83,7 @@ bst_test_install_t test_installers[] = { test_csip_notify_server_install, test_gmap_ugg_install, test_gmap_ugt_install, + test_ccp_call_control_server_install, NULL, }; diff --git a/tests/bsim/bluetooth/audio/test_scripts/ccp.sh b/tests/bsim/bluetooth/audio/test_scripts/ccp.sh new file mode 100755 index 0000000000000..cac742afbd84c --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/ccp.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +VERBOSITY_LEVEL=2 + +cd ${BSIM_OUT_PATH}/bin + +SIMULATION_ID="ccp" + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=ccp_call_control_server -rs=1 -D=1 + +# Simulation time should be larger than the WAIT_TIME in common.h +Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=1 -sim_length=60e6 $@ + +wait_for_background_jobs diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/CMakeLists.txt b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/CMakeLists.txt new file mode 100644 index 0000000000000..d7c38f708174a --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/CMakeLists.txt @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ccp_call_control_server_self_tets) + +set(ccp_call_control_server_path ${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_server) + +target_sources(app PRIVATE + ${ccp_call_control_server_path}/src/main.c +) + +target_sources(app PRIVATE + src/test_main.c +) + +zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) + +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ + ) diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/Kconfig.sysbuild b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/Kconfig.sysbuild new file mode 100644 index 0000000000000..f2b0bb8c607e2 --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/Kconfig.sysbuild @@ -0,0 +1,10 @@ +# Copyright (c) 2023-2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source "${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_server/Kconfig.sysbuild" + +config NATIVE_SIMULATOR_PRIMARY_MCU_INDEX + int + # Let's pass the test arguments to the application MCU test + # otherwise by default they would have gone to the net core. + default 0 if $(BOARD_TARGET_STRING) = "NRF5340BSIM_NRF5340_CPUAPP" diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/prj.conf b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/prj.conf new file mode 100644 index 0000000000000..2e887d8812bbd --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/prj.conf @@ -0,0 +1,2 @@ +# Please build using the sample configuration file: +# ${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_server/prj.conf diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/src/test_main.c b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/src/test_main.c new file mode 100644 index 0000000000000..d927d6f01cbcc --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/src/test_main.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "bs_types.h" +#include "bs_tracing.h" +#include "bs_utils.h" +#include "bstests.h" + +#define WAIT_TIME 10 /* Seconds */ + +#define PASS_THRESHOLD 100 /* Audio packets */ + +extern enum bst_result_t bst_result; + +#define FAIL(...) \ + do { \ + bst_result = Failed; \ + bs_trace_error_time_line(__VA_ARGS__); \ + } while (0) + +#define PASS(...) \ + do { \ + bst_result = Passed; \ + bs_trace_info_time(1, __VA_ARGS__); \ + } while (0) + +static void test_ccp_call_control_server_sample_init(void) +{ + bst_ticker_set_next_tick_absolute(WAIT_TIME * 1e6); + bst_result = In_progress; +} + +static void test_ccp_call_control_server_sample_tick(bs_time_t HW_device_time) +{ + /* TODO: Once the sample is more complete we can expand the pass criteria */ + PASS("CCP Call Control Server sample PASSED\n"); +} + +static const struct bst_test_instance test_sample[] = { + { + .test_id = "ccp_call_control_server", + .test_descr = "Test based on the CCP Call Control Server sample. " + "It expects to be connected to a compatible CCP Call Control Client, " + "waits for " STR( + WAIT_TIME) " seconds, and checks how " + "many audio packets have been received correctly", + .test_post_init_f = test_ccp_call_control_server_sample_init, + .test_tick_f = test_ccp_call_control_server_sample_tick, + }, + BSTEST_END_MARKER, +}; + +static struct bst_test_list * +test_ccp_call_control_server_sample_install(struct bst_test_list *tests) +{ + tests = bst_add_tests(tests, test_sample); + return tests; +} + +bst_test_install_t test_installers[] = {test_ccp_call_control_server_sample_install, NULL}; diff --git a/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/sysbuild.cmake b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/sysbuild.cmake new file mode 100644 index 0000000000000..4dc97dd85ac5f --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/call_control_server/sysbuild.cmake @@ -0,0 +1,6 @@ +# Copyright (c) 2023-2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +include(${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_server/sysbuild.cmake) + +native_simulator_set_primary_mcu_index(${DEFAULT_IMAGE} ${NET_APP}) diff --git a/tests/bsim/bluetooth/audio_samples/ccp/compile.sh b/tests/bsim/bluetooth/audio_samples/ccp/compile.sh new file mode 100755 index 0000000000000..186805e7ec416 --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/compile.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Copyright 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +#set -x #uncomment this line for debugging +set -ue + +: "${ZEPHYR_BASE:?ZEPHYR_BASE must be set to point to the zephyr root directory}" + +source ${ZEPHYR_BASE}/tests/bsim/compile.source + +if [ "${BOARD_TS}" == "nrf5340bsim_nrf5340_cpuapp" ]; then + app=tests/bsim/bluetooth/audio_samples/ccp/call_control_server \ + sample=${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_server \ + conf_file=${sample}/prj.conf \ + conf_overlay=${sample}/boards/nrf5340_audio_dk_nrf5340_cpuapp.conf \ + exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile +else + app=tests/bsim/bluetooth/audio_samples/ccp/call_control_server \ + sample=${ZEPHYR_BASE}/samples/bluetooth/ccp_call_control_server \ + conf_file=${sample}/prj.conf \ + conf_overlay=${sample}/overlay-bt_ll_sw_split.conf \ + exe_name=bs_${BOARD_TS}_${app}_prj_conf sysbuild=1 compile +fi + +wait_for_background_jobs diff --git a/tests/bsim/bluetooth/audio_samples/ccp/tests_scripts/ccp.sh b/tests/bsim/bluetooth/audio_samples/ccp/tests_scripts/ccp.sh new file mode 100755 index 0000000000000..87564607df7ab --- /dev/null +++ b/tests/bsim/bluetooth/audio_samples/ccp/tests_scripts/ccp.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Copyright 2023-2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Simple selfchecking test for the CCP samples. + +simulation_id="ccp_samples_test" +verbosity_level=2 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_samples_ccp_call_control_server_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=0 -RealEncryption=1 -testid=ccp_call_control_server + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \ + -D=1 -sim_length=20e6 $@ -argschannel -at=40 + +wait_for_background_jobs #Wait for all programs in background and return != 0 if any fails diff --git a/tests/bsim/bluetooth/audio_samples/compile.sh b/tests/bsim/bluetooth/audio_samples/compile.sh index cfc69b1605745..f9f3300d2d40f 100755 --- a/tests/bsim/bluetooth/audio_samples/compile.sh +++ b/tests/bsim/bluetooth/audio_samples/compile.sh @@ -14,5 +14,6 @@ source ${ZEPHYR_BASE}/tests/bsim/compile.source run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/audio_samples/bap_broadcast_sink/compile.sh run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/audio_samples/bap_unicast_client/compile.sh run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/audio_samples/cap/compile.sh +run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/audio_samples/ccp/compile.sh wait_for_background_jobs