diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 8e95117a252..a55b5ccded8 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -480,6 +480,7 @@ Bluetooth Host: - tests/bsim/bluetooth/hci_uart/ - tests/bsim/bluetooth/ll/ - tests/bsim/bluetooth/mesh/ + - tests/bsim/bluetooth/tester/src/audio/ labels: - "area: Bluetooth Host" - "area: Bluetooth" @@ -534,6 +535,7 @@ Bluetooth Audio: - tests/bluetooth/audio/ - tests/bsim/bluetooth/audio/ - tests/bsim/bluetooth/audio_samples/ + - tests/bsim/bluetooth/tester/src/audio/ - tests/bluetooth/shell/audio.conf - tests/bluetooth/tester/overlay-le-audio.conf - tests/bluetooth/tester/overlay-bt_ll_sw_split.conf diff --git a/tests/bluetooth/tester/testcase.yaml b/tests/bluetooth/tester/testcase.yaml index 3f7df32759c..9d3f7f758cc 100644 --- a/tests/bluetooth/tester/testcase.yaml +++ b/tests/bluetooth/tester/testcase.yaml @@ -61,6 +61,17 @@ tests: tags: bluetooth harness: bluetooth sysbuild: true + bluetooth.general.tester_le_audio_bsim: + build_only: true + platform_allow: + - nrf52_bsim/native + - nrf5340bsim/nrf5340/cpuapp + extra_args: + - EXTRA_CONF_FILE="overlay-le-audio.conf" + harness: bsim + harness_config: + bsim_exe_name: tests_bluetooth_tester_le_audio_prj_conf + sysbuild: true bluetooth.general.tester_mesh: build_only: true platform_allow: diff --git a/tests/bsim/bluetooth/tester/CMakeLists.txt b/tests/bsim/bluetooth/tester/CMakeLists.txt index 136e98e5f99..26595b63f4d 100644 --- a/tests/bsim/bluetooth/tester/CMakeLists.txt +++ b/tests/bsim/bluetooth/tester/CMakeLists.txt @@ -24,6 +24,8 @@ zephyr_include_directories( target_sources(app PRIVATE src/bsim_btp.c src/test_main.c + src/audio/vcp_central.c + src/audio/vcp_peripheral.c src/host/gap_central.c src/host/gap_peripheral.c ) diff --git a/tests/bsim/bluetooth/tester/src/audio/vcp_central.c b/tests/bsim/bluetooth/tester/src/audio/vcp_central.c new file mode 100644 index 00000000000..d16a0d04b6e --- /dev/null +++ b/tests/bsim/bluetooth/tester/src/audio/vcp_central.c @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include +#include +#include + +#include "babblekit/testcase.h" +#include "bstests.h" + +#include "btp/btp.h" +#include "bsim_btp.h" + +LOG_MODULE_REGISTER(bsim_vcp_central, CONFIG_BSIM_BTTESTER_LOG_LEVEL); + +static void test_vcp_central(void) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + bt_addr_le_t remote_addr; + + bsim_btp_uart_init(); + + bsim_btp_wait_for_evt(BTP_SERVICE_ID_CORE, BTP_CORE_EV_IUT_READY, NULL); + + bsim_btp_core_register(BTP_SERVICE_ID_GAP); + bsim_btp_core_register(BTP_SERVICE_ID_VCP); + bsim_btp_core_register(BTP_SERVICE_ID_VOCS); + bsim_btp_core_register(BTP_SERVICE_ID_AICS); + + bsim_btp_gap_start_discovery(BTP_GAP_DISCOVERY_FLAG_LE); + bsim_btp_wait_for_gap_device_found(&remote_addr); + bt_addr_le_to_str(&remote_addr, addr_str, sizeof(addr_str)); + LOG_INF("Found remote device %s", addr_str); + + bsim_btp_gap_stop_discovery(); + bsim_btp_gap_connect(&remote_addr, BTP_GAP_ADDR_TYPE_IDENTITY); + bsim_btp_wait_for_gap_device_connected(NULL); + LOG_INF("Device %s connected", addr_str); + + bsim_btp_gap_pair(&remote_addr); + bsim_btp_wait_for_gap_sec_level_changed(NULL, NULL); + + bsim_btp_vcp_discover(&remote_addr); + bsim_btp_wait_for_vcp_discovered(NULL); + + const uint8_t new_vol = 123; + uint8_t ev_vol; + + bsim_btp_vcp_ctlr_set_vol(&remote_addr, new_vol); + bsim_btp_wait_for_vcp_state(NULL, &ev_vol); + TEST_ASSERT(ev_vol == new_vol, "%u != %u", ev_vol, new_vol); + + const int16_t new_offset = -5; + int16_t ev_offset; + + bsim_btp_vocs_state_set(&remote_addr, new_offset); + bsim_btp_wait_for_vocs_state(NULL, &ev_offset); + TEST_ASSERT(ev_offset == new_offset, "%d != %d", ev_offset, new_offset); + + const int8_t new_gain = 5; + int8_t ev_gain; + + bsim_btp_aics_set_gain(&remote_addr, new_gain); + bsim_btp_wait_for_aics_state(NULL, &ev_gain); + TEST_ASSERT(ev_gain == new_gain, "%d != %d", ev_gain, new_gain); + + bsim_btp_gap_disconnect(&remote_addr); + bsim_btp_wait_for_gap_device_disconnected(NULL); + LOG_INF("Device %s disconnected", addr_str); + + TEST_PASS("PASSED\n"); +} + +static const struct bst_test_instance test_sample[] = { + { + .test_id = "vcp_central", + .test_descr = "Smoketest for the VCP central BT Tester behavior", + .test_main_f = test_vcp_central, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_vcp_central_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_sample); +} diff --git a/tests/bsim/bluetooth/tester/src/audio/vcp_peripheral.c b/tests/bsim/bluetooth/tester/src/audio/vcp_peripheral.c new file mode 100644 index 00000000000..9fdfbcaf50e --- /dev/null +++ b/tests/bsim/bluetooth/tester/src/audio/vcp_peripheral.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include +#include +#include + +#include "babblekit/testcase.h" +#include "bstests.h" + +#include "btp/btp.h" +#include "bsim_btp.h" + +LOG_MODULE_REGISTER(bsim_vcp_peripheral, CONFIG_BSIM_BTTESTER_LOG_LEVEL); + +static void test_vcp_peripheral(void) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + bt_addr_le_t remote_addr; + + bsim_btp_uart_init(); + + bsim_btp_wait_for_evt(BTP_SERVICE_ID_CORE, BTP_CORE_EV_IUT_READY, NULL); + + bsim_btp_core_register(BTP_SERVICE_ID_GAP); + bsim_btp_core_register(BTP_SERVICE_ID_VCS); + bsim_btp_core_register(BTP_SERVICE_ID_VOCS); + bsim_btp_core_register(BTP_SERVICE_ID_AICS); + + bsim_btp_gap_set_discoverable(BTP_GAP_GENERAL_DISCOVERABLE); + bsim_btp_gap_start_advertising(0U, 0U, NULL, BT_HCI_OWN_ADDR_PUBLIC); + bsim_btp_wait_for_gap_device_connected(&remote_addr); + bt_addr_le_to_str(&remote_addr, addr_str, sizeof(addr_str)); + LOG_INF("Device %s connected", addr_str); + bsim_btp_wait_for_gap_device_disconnected(NULL); + LOG_INF("Device %s disconnected", addr_str); + + TEST_PASS("PASSED\n"); +} + +static const struct bst_test_instance test_sample[] = { + { + .test_id = "vcp_peripheral", + .test_descr = "Smoketest for the VCP peripheral BT Tester behavior", + .test_main_f = test_vcp_peripheral, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_vcp_peripheral_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_sample); +} diff --git a/tests/bsim/bluetooth/tester/src/bsim_btp.c b/tests/bsim/bluetooth/tester/src/bsim_btp.c index 75bc09d4987..30b8f4d1981 100644 --- a/tests/bsim/bluetooth/tester/src/bsim_btp.c +++ b/tests/bsim/bluetooth/tester/src/bsim_btp.c @@ -192,6 +192,8 @@ static bool is_valid_gap_packet_len(const struct btp_hdr *hdr, struct net_buf_si } case BTP_GAP_EV_PERIODIC_TRANSFER_RECEIVED: return buf_simple->len == sizeof(struct btp_gap_ev_periodic_transfer_received_ev); + case BTP_GAP_EV_ENCRYPTION_CHANGE: + return buf_simple->len == sizeof(struct btp_gap_encryption_change_ev); default: LOG_ERR("Unhandled opcode 0x%02X", hdr->opcode); return false; diff --git a/tests/bsim/bluetooth/tester/src/bsim_btp.h b/tests/bsim/bluetooth/tester/src/bsim_btp.h index faad454eb3d..fe744113404 100644 --- a/tests/bsim/bluetooth/tester/src/bsim_btp.h +++ b/tests/bsim/bluetooth/tester/src/bsim_btp.h @@ -3,10 +3,16 @@ * * SPDX-License-Identifier: Apache-2.0 */ + +#ifndef BSIM_BTP_H_ +#define BSIM_BTP_H_ + #include #include #include +#include #include +#include #include "babblekit/testcase.h" @@ -198,3 +204,202 @@ static inline void bsim_btp_wait_for_gap_device_disconnected(bt_addr_le_t *addre net_buf_unref(buf); } + +static inline void bsim_btp_gap_pair(const bt_addr_le_t *address) +{ + struct btp_gap_pair_cmd *cmd; + struct btp_hdr *cmd_hdr; + + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_GAP; + cmd_hdr->opcode = BTP_GAP_PAIR; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + bt_addr_le_copy(&cmd->address, address); + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + bsim_btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void bsim_btp_wait_for_gap_sec_level_changed(bt_addr_le_t *address, + uint8_t *sec_level) +{ + struct btp_gap_sec_level_changed_ev *ev; + struct net_buf *buf; + + bsim_btp_wait_for_evt(BTP_SERVICE_ID_GAP, BTP_GAP_EV_SEC_LEVEL_CHANGED, &buf); + ev = net_buf_pull_mem(buf, sizeof(*ev)); + + if (address != NULL) { + bt_addr_le_copy(address, &ev->address); + } + + if (sec_level != NULL) { + *sec_level = ev->sec_level; + } + + net_buf_unref(buf); +} + +static inline void bsim_btp_vcp_discover(const bt_addr_le_t *address) +{ + struct btp_vcp_discover_cmd *cmd; + struct btp_hdr *cmd_hdr; + + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_VCP; + cmd_hdr->opcode = BTP_VCP_VOL_CTLR_DISCOVER; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + bt_addr_le_copy(&cmd->address, address); + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + bsim_btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void bsim_btp_wait_for_vcp_discovered(bt_addr_le_t *address) +{ + struct btp_vcp_discovered_ev *ev; + struct net_buf *buf; + + bsim_btp_wait_for_evt(BTP_SERVICE_ID_VCP, BTP_VCP_DISCOVERED_EV, &buf); + ev = net_buf_pull_mem(buf, sizeof(*ev)); + + TEST_ASSERT(ev->att_status == BT_ATT_ERR_SUCCESS); + + if (address != NULL) { + bt_addr_le_copy(address, &ev->address); + } + + net_buf_unref(buf); +} + +static inline void bsim_btp_vcp_ctlr_set_vol(const bt_addr_le_t *address, uint8_t volume) +{ + struct btp_vcp_ctlr_set_vol_cmd *cmd; + struct btp_hdr *cmd_hdr; + + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_VCP; + cmd_hdr->opcode = BTP_VCP_VOL_CTLR_SET_VOL; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + bt_addr_le_copy(&cmd->address, address); + cmd->volume = volume; + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + bsim_btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void bsim_btp_wait_for_vcp_state(bt_addr_le_t *address, uint8_t *volume) +{ + struct btp_vcp_state_ev *ev; + struct net_buf *buf; + + bsim_btp_wait_for_evt(BTP_SERVICE_ID_VCP, BTP_VCP_STATE_EV, &buf); + ev = net_buf_pull_mem(buf, sizeof(*ev)); + + TEST_ASSERT(ev->att_status == BT_ATT_ERR_SUCCESS); + + if (address != NULL) { + bt_addr_le_copy(address, &ev->address); + } + + if (volume != NULL) { + *volume = ev->volume; + } + + net_buf_unref(buf); +} + +static inline void bsim_btp_vocs_state_set(const bt_addr_le_t *address, int16_t offset) +{ + struct btp_vocs_offset_set_cmd *cmd; + struct btp_hdr *cmd_hdr; + + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_VOCS; + cmd_hdr->opcode = BTP_VOCS_OFFSET_STATE_SET; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + bt_addr_le_copy(&cmd->address, address); + cmd->offset = sys_cpu_to_le16(offset); + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + bsim_btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void bsim_btp_wait_for_vocs_state(bt_addr_le_t *address, int16_t *offset) +{ + struct btp_vocs_offset_state_ev *ev; + struct net_buf *buf; + + bsim_btp_wait_for_evt(BTP_SERVICE_ID_VOCS, BTP_VOCS_OFFSET_STATE_EV, &buf); + ev = net_buf_pull_mem(buf, sizeof(*ev)); + + TEST_ASSERT(ev->att_status == BT_ATT_ERR_SUCCESS); + + if (address != NULL) { + bt_addr_le_copy(address, &ev->address); + } + + if (offset != NULL) { + *offset = sys_le16_to_cpu(ev->offset); + } + + net_buf_unref(buf); +} + +static inline void bsim_btp_aics_set_gain(const bt_addr_le_t *address, int8_t gain) +{ + struct btp_aics_set_gain_cmd *cmd; + struct btp_hdr *cmd_hdr; + + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_AICS; + cmd_hdr->opcode = BTP_AICS_SET_GAIN; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + bt_addr_le_copy(&cmd->address, address); + cmd->gain = gain; + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + bsim_btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void bsim_btp_wait_for_aics_state(bt_addr_le_t *address, int8_t *gain) +{ + struct btp_aics_state_ev *ev; + struct net_buf *buf; + + bsim_btp_wait_for_evt(BTP_SERVICE_ID_AICS, BTP_AICS_STATE_EV, &buf); + ev = net_buf_pull_mem(buf, sizeof(*ev)); + + TEST_ASSERT(ev->att_status == BT_ATT_ERR_SUCCESS); + + if (address != NULL) { + bt_addr_le_copy(address, &ev->address); + } + + if (gain != NULL) { + *gain = ev->gain; + } + + net_buf_unref(buf); +} +#endif /* BSIM_BTP_H_ */ diff --git a/tests/bsim/bluetooth/tester/src/test_main.c b/tests/bsim/bluetooth/tester/src/test_main.c index 12328aebd4f..e0769aa9434 100644 --- a/tests/bsim/bluetooth/tester/src/test_main.c +++ b/tests/bsim/bluetooth/tester/src/test_main.c @@ -9,10 +9,14 @@ extern struct bst_test_list *test_gap_central_install(struct bst_test_list *tests); extern struct bst_test_list *test_gap_peripheral_install(struct bst_test_list *tests); +extern struct bst_test_list *test_vcp_central_install(struct bst_test_list *tests); +extern struct bst_test_list *test_vcp_peripheral_install(struct bst_test_list *tests); bst_test_install_t test_installers[] = { test_gap_central_install, test_gap_peripheral_install, + test_vcp_central_install, + test_vcp_peripheral_install, NULL, }; diff --git a/tests/bsim/bluetooth/tester/tests_scripts/vcp.sh b/tests/bsim/bluetooth/tester/tests_scripts/vcp.sh new file mode 100755 index 00000000000..a61a9ee0c8e --- /dev/null +++ b/tests/bsim/bluetooth/tester/tests_scripts/vcp.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Copyright 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Smoketest for VCP BTP commands with the BT tester + +simulation_id="tester_vcp" +verbosity_level=2 +EXECUTE_TIMEOUT=100 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +UART_DIR=/tmp/bs_${USER}/${simulation_id}/ +UART_PER=${UART_DIR}/peripheral +UART_CEN=${UART_DIR}/central + +# Central BT Tester +Execute ./bs_${BOARD_TS}_tests_bluetooth_tester_le_audio_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -rs=10 -d=0 -RealEncryption=1 \ + -uart0_fifob_rxfile=${UART_CEN}.tx -uart0_fifob_txfile=${UART_CEN}.rx + +# Central Upper Tester +Execute ./bs_nrf52_bsim_native_tests_bsim_bluetooth_tester_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -rs=21 -d=10 -RealEncryption=1 -testid=vcp_central \ + -nosim -uart0_fifob_rxfile=${UART_CEN}.rx -uart0_fifob_txfile=${UART_CEN}.tx + +# Peripheral BT Tester +Execute ./bs_${BOARD_TS}_tests_bluetooth_tester_le_audio_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -rs=32 -d=1 -RealEncryption=1 \ + -uart0_fifob_rxfile=${UART_PER}.tx -uart0_fifob_txfile=${UART_PER}.rx + +# Peripheral Upper Tester +Execute ./bs_nrf52_bsim_native_tests_bsim_bluetooth_tester_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -rs=43 -d=11 -RealEncryption=1 -testid=vcp_peripheral \ + -nosim -uart0_fifob_rxfile=${UART_PER}.rx -uart0_fifob_txfile=${UART_PER}.tx + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} -D=2 -sim_length=20e6 $@ + +wait_for_background_jobs # Wait for all programs in background and return != 0 if any fails