From 91c6f1e357199c1a77e86153a98fd86b335a6813 Mon Sep 17 00:00:00 2001 From: Timothy Keys Date: Thu, 20 Aug 2026 14:44:49 +0100 Subject: [PATCH 1/2] bluetooth: Add support for Channel Classification This adds the Channel Classification feature which the central can use to receive channel classification reports from the peripheral. Signed-off-by: Timothy Keys --- .../releases/release-notes-changelog.rst | 1 + include/bluetooth/hci_vs_sdc.h | 10 +++++++++ subsys/bluetooth/controller/Kconfig | 8 +++++++ subsys/bluetooth/controller/hci_driver.c | 17 ++++++++++++++ subsys/bluetooth/controller/hci_internal.c | 22 +++++++++++++++++++ subsys/bluetooth/hci_vs_sdc.c | 7 ++++++ 6 files changed, 65 insertions(+) diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index dde78ff27466..5a01ff220af3 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -175,6 +175,7 @@ Bluetooth® LE * Added the :kconfig:option:`CONFIG_BT_HCI_SUPPORT_DEPRECATED_COMMANDS` Kconfig option to support deprecated HCI commands. The option is disabled by default, and enabling it may cause deprecation warnings or errors during compilation. +* Added support for LE Channel Classification in the SoftDevice Controller through the :kconfig:option:`CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION` Kconfig option. Bluetooth Mesh -------------- diff --git a/include/bluetooth/hci_vs_sdc.h b/include/bluetooth/hci_vs_sdc.h index 9e8a2e0883c9..a794b5942898 100644 --- a/include/bluetooth/hci_vs_sdc.h +++ b/include/bluetooth/hci_vs_sdc.h @@ -404,6 +404,16 @@ int hci_vs_sdc_set_event_start_task(const sdc_hci_cmd_vs_set_event_start_task_t int hci_vs_sdc_conn_anchor_point_update_event_report_enable( const sdc_hci_cmd_vs_conn_anchor_point_update_event_report_enable_t *params); +/** @brief Enable channel classification reporting on a connection. + * + * For the complete API description, see sdc_hci_cmd_vs_channel_reporting_enable(). + * + * @param[in] params Input parameters. + * + * @return 0 on success or negative error value on failure. + */ +int hci_vs_sdc_channel_reporting_enable(const sdc_hci_cmd_vs_channel_reporting_enable_t *params); + /** @brief Enable Periodic Adv Event Counter Reports. * * For the complete API description, diff --git a/subsys/bluetooth/controller/Kconfig b/subsys/bluetooth/controller/Kconfig index 8cc80e57300c..8235181c7fae 100644 --- a/subsys/bluetooth/controller/Kconfig +++ b/subsys/bluetooth/controller/Kconfig @@ -782,6 +782,14 @@ config BT_CTLR_LE_FLUSHABLE_ACL_DATA help Enable support for Bluetooth 6.2 LE Flushable ACL Data in the Controller. +config BT_CTLR_CHANNEL_CLASSIFICATION + bool "LE Channel Classification [EXPERIMENTAL]" + depends on BT_CONN + select EXPERIMENTAL + help + Enable support for the Bluetooth 5.3 LE Channel Classification feature + in the controller. + config BT_LL_SOFTDEVICE_INIT_PRIORITY int default 53 if MPSL_USE_EXTERNAL_CLOCK_CONTROL diff --git a/subsys/bluetooth/controller/hci_driver.c b/subsys/bluetooth/controller/hci_driver.c index 79a9a20897a0..a36e18ae555b 100644 --- a/subsys/bluetooth/controller/hci_driver.c +++ b/subsys/bluetooth/controller/hci_driver.c @@ -193,6 +193,13 @@ BUILD_ASSERT(!IS_ENABLED(CONFIG_BT_PERIPHERAL) || #define SDC_SUBRATING_MEM_SIZE 0 #endif +#if defined(CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION) +#define SDC_CHANNEL_CLASSIFICATION_MEM_SIZE \ + SDC_MEM_CHANNEL_CLASSIFICATION(SDC_CENTRAL_COUNT + PERIPHERAL_COUNT) +#else +#define SDC_CHANNEL_CLASSIFICATION_MEM_SIZE 0 +#endif + #if defined(CONFIG_BT_CTLR_SYNC_TRANSFER_RECEIVER) || defined(CONFIG_BT_CTLR_SYNC_TRANSFER_SENDER) #define SDC_SYNC_TRANSFER_MEM_SIZE SDC_MEM_SYNC_TRANSFER(SDC_CENTRAL_COUNT + PERIPHERAL_COUNT) #else @@ -300,6 +307,7 @@ BUILD_ASSERT(!IS_ENABLED(CONFIG_BT_PERIPHERAL) || (SDC_ADV_SET_MEM_SIZE) + \ (SDC_LE_POWER_CONTROL_MEM_SIZE) + \ (SDC_SUBRATING_MEM_SIZE) + \ + (SDC_CHANNEL_CLASSIFICATION_MEM_SIZE) + \ (SDC_SYNC_TRANSFER_MEM_SIZE) + \ (SDC_FRAME_SPACE_UPDATE_MEM_SIZE) + \ (SDC_SHORTER_CONNECTION_INTERVALS_MEM_SIZE) + \ @@ -917,6 +925,15 @@ static void configure_supported_features(void) } } + if (IS_ENABLED(CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION)) { + if (IS_ENABLED(CONFIG_BT_CENTRAL)) { + sdc_support_channel_classification_central(); + } + if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) { + sdc_support_channel_classification_peripheral(); + } + } + if (IS_ENABLED(CONFIG_BT_CTLR_FRAME_SPACE_UPDATE)) { if (IS_ENABLED(CONFIG_BT_CENTRAL)) { sdc_support_frame_space_update_central(); diff --git a/subsys/bluetooth/controller/hci_internal.c b/subsys/bluetooth/controller/hci_internal.c index c8f4ec77d038..49a4286a5ec3 100644 --- a/subsys/bluetooth/controller/hci_internal.c +++ b/subsys/bluetooth/controller/hci_internal.c @@ -90,6 +90,9 @@ static bool command_generates_command_complete_event(uint16_t hci_opcode) #if defined(CONFIG_BT_CTLR_SHORTER_CONNECTION_INTERVALS) case SDC_HCI_OPCODE_CMD_LE_CONN_RATE_REQUEST: #endif /* CONFIG_BT_CTLR_SHORTER_CONNECTION_INTERVALS */ +#if defined(CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION) + case SDC_HCI_OPCODE_CMD_VS_CHANNEL_REPORTING_ENABLE: +#endif /* CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION */ return false; default: return true; @@ -715,6 +718,11 @@ void hci_internal_supported_commands(sdc_hci_ip_supported_commands_t *cmds) cmds->hci_read_automatic_flush_timeout = 1; cmds->hci_write_automatic_flush_timeout = 1; #endif /* CONFIG_BT_CTLR_LE_FLUSHABLE_ACL_DATA */ + +#if defined(CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION) + cmds->hci_read_afh_channel_assessment_mode = 1; + cmds->hci_write_afh_channel_assessment_mode = 1; +#endif } #if defined(CONFIG_BT_HCI_VS) @@ -896,6 +904,15 @@ static uint8_t controller_and_baseband_cmd_put(uint8_t const * const cmd, (void *)event_out_params); #endif +#if defined(CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION) + case SDC_HCI_OPCODE_CMD_CB_READ_AFH_CHANNEL_ASSESSMENT_MODE: + *param_length_out += + sizeof(sdc_hci_cmd_cb_read_afh_channel_assessment_mode_return_t); + return sdc_hci_cmd_cb_read_afh_channel_assessment_mode((void *)event_out_params); + case SDC_HCI_OPCODE_CMD_CB_WRITE_AFH_CHANNEL_ASSESSMENT_MODE: + return sdc_hci_cmd_cb_write_afh_channel_assessment_mode((void *)cmd_params); +#endif + default: return BT_HCI_ERR_UNKNOWN_CMD; } @@ -1832,6 +1849,11 @@ static uint8_t vs_cmd_put(uint8_t const *const cmd, uint8_t *const raw_event_out return sdc_hci_cmd_vs_conn_anchor_point_update_event_report_enable( (sdc_hci_cmd_vs_conn_anchor_point_update_event_report_enable_t const *)cmd_params); #endif +#if defined(CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION) && defined(CONFIG_BT_CENTRAL) + case SDC_HCI_OPCODE_CMD_VS_CHANNEL_REPORTING_ENABLE: + return sdc_hci_cmd_vs_channel_reporting_enable( + (sdc_hci_cmd_vs_channel_reporting_enable_t const *)cmd_params); +#endif #if defined(CONFIG_BT_PER_ADV) case SDC_HCI_OPCODE_CMD_VS_ENABLE_PERIODIC_ADV_EVENT_COUNTER_REPORTS: return sdc_hci_cmd_vs_enable_periodic_adv_event_counter_reports( diff --git a/subsys/bluetooth/hci_vs_sdc.c b/subsys/bluetooth/hci_vs_sdc.c index 576a4552d639..dc205b73d8b0 100644 --- a/subsys/bluetooth/hci_vs_sdc.c +++ b/subsys/bluetooth/hci_vs_sdc.c @@ -347,6 +347,13 @@ int hci_vs_sdc_conn_anchor_point_update_event_report_enable( sizeof(*params)); } +int hci_vs_sdc_channel_reporting_enable(const sdc_hci_cmd_vs_channel_reporting_enable_t *params) +{ + return hci_vs_cmd_no_rsp(SDC_HCI_OPCODE_CMD_VS_CHANNEL_REPORTING_ENABLE, + params, + sizeof(*params)); +} + int hci_vs_sdc_enable_periodic_adv_event_counter_reports( const sdc_hci_cmd_vs_enable_periodic_adv_event_counter_reports_t *params) { From cc509a1b8e063b1e43d23b6845df7e8176bc8e79 Mon Sep 17 00:00:00 2001 From: Timothy Keys Date: Thu, 20 Aug 2026 14:53:04 +0100 Subject: [PATCH 2/2] samples: bluetooth: Add Channel Classification sample This sample demonstrates a usecase for channel classification. The sample shows the central receiving channel classification reports from the peripheral and applying them in a channel map update. Signed-off-by: Timothy Keys --- CODEOWNERS | 2 + .../releases/release-notes-changelog.rst | 2 + .../channel_classification/CMakeLists.txt | 12 + .../channel_classification/Kconfig.sysbuild | 13 + .../channel_classification/README.rst | 127 ++++++ .../bluetooth/channel_classification/prj.conf | 24 ++ .../channel_classification/src/main.c | 363 ++++++++++++++++++ .../sysbuild/ipc_radio/prj.conf | 10 + .../channel_classification/tests.yaml | 28 ++ 9 files changed, 581 insertions(+) create mode 100644 samples/bluetooth/channel_classification/CMakeLists.txt create mode 100644 samples/bluetooth/channel_classification/Kconfig.sysbuild create mode 100644 samples/bluetooth/channel_classification/README.rst create mode 100644 samples/bluetooth/channel_classification/prj.conf create mode 100644 samples/bluetooth/channel_classification/src/main.c create mode 100644 samples/bluetooth/channel_classification/sysbuild/ipc_radio/prj.conf create mode 100644 samples/bluetooth/channel_classification/tests.yaml diff --git a/CODEOWNERS b/CODEOWNERS index bc1bc92c7116..e03801a2abc5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -517,6 +517,7 @@ /samples/bluetooth/central_nfc_pairing/ @nrfconnect/ncs-blenders /samples/bluetooth/central_smp_client/ @nrfconnect/ncs-eris /samples/bluetooth/central_uart/ @nrfconnect/ncs-blenders +/samples/bluetooth/channel_classification/ @nrfconnect/ncs-dragoon /samples/bluetooth/channel_sounding/ @nrfconnect/ncs-dragoon /samples/bluetooth/conn_time_sync/ @nrfconnect/ncs-dragoon /samples/bluetooth/direction_finding_central/ @nrfconnect/ncs-dragoon @@ -642,6 +643,7 @@ /samples/bluetooth/central_nfc_pairing/*.rst @nrfconnect/ncs-blenders-doc /samples/bluetooth/central_smp_client/*.rst @nrfconnect/ncs-eris-doc /samples/bluetooth/central_uart/*.rst @nrfconnect/ncs-blenders-doc +/samples/bluetooth/channel_classification/*.rst @nrfconnect/ncs-dragoon-doc /samples/bluetooth/channel_sounding/ras_initiator/*.rst @nrfconnect/ncs-dragoon-doc /samples/bluetooth/channel_sounding/ras_reflector/*.rst @nrfconnect/ncs-dragoon-doc /samples/bluetooth/channel_sounding/ipt_initiator/*.rst @nrfconnect/ncs-dragoon-doc diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index 5a01ff220af3..60ecb98ea22f 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -335,6 +335,8 @@ Bluetooth samples * Updated the minimum supported connection interval from 875 µs to 750 µs in the HID SCI configuration. * Enabled the Frame Space Update feature in the single peripheral HID SCI configuration. +* Added the :ref:`ble_channel_classification` sample that demonstrates peripheral-initiated channel map updates and central reception of channel classification reports. + Bluetooth Mesh samples ---------------------- diff --git a/samples/bluetooth/channel_classification/CMakeLists.txt b/samples/bluetooth/channel_classification/CMakeLists.txt new file mode 100644 index 000000000000..a1d6087d5467 --- /dev/null +++ b/samples/bluetooth/channel_classification/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (c) 2026 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(bluetooth_channel_classification) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/bluetooth/channel_classification/Kconfig.sysbuild b/samples/bluetooth/channel_classification/Kconfig.sysbuild new file mode 100644 index 000000000000..6d2e55f9ee87 --- /dev/null +++ b/samples/bluetooth/channel_classification/Kconfig.sysbuild @@ -0,0 +1,13 @@ +# +# Copyright (c) 2026 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config NRF_DEFAULT_IPC_RADIO + default y + +config NETCORE_IPC_RADIO_BT_HCI_IPC + default y + +source "share/sysbuild/Kconfig" diff --git a/samples/bluetooth/channel_classification/README.rst b/samples/bluetooth/channel_classification/README.rst new file mode 100644 index 000000000000..5ddfafbac065 --- /dev/null +++ b/samples/bluetooth/channel_classification/README.rst @@ -0,0 +1,127 @@ +.. _ble_channel_classification: + +.. ncs-sample:: + :title: Bluetooth: Channel Classification + +This sample demonstrates LE Channel Classification reporting between a central and a peripheral. + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +You can use any combination of the development kits mentioned above in your testing setup. + +Additionally, the sample requires a connection to a computer with a serial terminal for each of the development kits. + +Overview +******** + +When connected, the peripheral cycles through predefined host channel maps, changing every five seconds. + +The central enables channel classification reporting using :c:func:`hci_vs_sdc_channel_reporting_enable` and handles the received reports in a local HCI VS event callback. + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/bluetooth/channel_classification` + +.. include:: /includes/build_and_run.txt + +.. |sample_or_app| replace:: sample +.. |ipc_radio_dir| replace:: :file:`sysbuild/ipc_radio` + +.. include:: /includes/ipc_radio_conf.txt + +Testing +======= + +After programming the sample to both development kits, test it by performing the following steps: + +1. Connect to both kits with a terminal emulator (for example, the `Serial Terminal app`_). + See :ref:`test_and_optimize` for the required settings and steps. +#. Reset both kits. +#. In one of the terminal emulators, type ``c`` to start the application on the connected board in the central role. +#. In the other terminal emulator, type ``p`` to start the application in the peripheral role. +#. Observe that the kits establish a connection. +#. On the peripheral, observe a new channel map printed every five seconds. +#. On the central, observe matching channel maps applied from the received classification reports. + +Sample output +============= + +* Example peripheral output:: + + *** Booting nRF Connect SDK v3.4.99-b15b0977d5d7 *** + *** Using Zephyr OS v4.4.99-bf52a9edec7e *** + I: Starting Bluetooth Channel Classification sample + I: SoftDevice Controller build revision: + I: 2e 0b 04 cf 06 5e 2e 8f |.....^.. + I: a8 14 89 4d b6 1b 7a 8c |...M..z. + I: d4 e4 bb 1f |.... + I: HW Platform: Nordic Semiconductor (0x0002) + I: HW Variant: nRF54Lx (0x0005) + I: Firmware: Standard Bluetooth controller (0x00) Version 46.1035 Build 777914063 + I: HCI transport: SDC + I: Identity: E9:A2:74:BE:64:BA (random) + I: HCI: version 6.3 (0x11) revision 0x306a, manufacturer 0x0059 + I: LMP: version 6.3 (0x11) subver 0x306a + I: Bluetooth initialized + I: Choose device role - type c (central) or p (peripheral): + I: + I: Selected Peripheral + I: Advertising successfully started + I: Connected as peripheral + I: Using channel map 0 + I: Peripheral channel map ff ff ff ff 1f + I: Using channel map 1 + I: Peripheral channel map ff fb ef bf 1f + I: Using channel map 2 + I: Peripheral channel map df 7f ff fd 1f + +* Example central output:: + + *** Booting nRF Connect SDK v3.4.99-b15b0977d5d7 *** + *** Using Zephyr OS v4.4.99-bf52a9edec7e *** + I: Starting Bluetooth Channel Classification sample + I: SoftDevice Controller build revision: + I: 2e 0b 04 cf 06 5e 2e 8f |.....^.. + I: a8 14 89 4d b6 1b 7a 8c |...M..z. + I: d4 e4 bb 1f |.... + I: HW Platform: Nordic Semiconductor (0x0002) + I: HW Variant: nRF54Lx (0x0005) + I: Firmware: Standard Bluetooth controller (0x00) Version 46.1035 Build 777914063 + I: HCI transport: SDC + I: Identity: F1:6C:D4:5E:80:EA (random) + I: HCI: version 6.3 (0x11) revision 0x306a, manufacturer 0x0059 + I: LMP: version 6.3 (0x11) subver 0x306a + I: Bluetooth initialized + I: Choose device role - type c (central) or p (peripheral): + I: + I: Selected Central + I: Scanning successfully started + I: Filters matched. Address: E9:A2:74:BE:64:BA (random) connectable: 1 + I: Connected as central + I: Channel classification reporting enabled + I: Central channel map from report ff ff ff ff 1f + I: Central applied peripheral channel classification + I: Central channel map from report ff fb ef bf 1f + I: Central applied peripheral channel classification + I: Central channel map from report df 7f ff fd 1f + +Dependencies +************ + +This sample uses the following |NCS| library: + +* :file:`include/bluetooth/hci_vs_sdc.h` + +This sample uses the following `sdk-nrfxlib`_ libraries: + +* :ref:`nrfxlib:softdevice_controller` + +In addition, it uses the following Zephyr libraries: + +* :ref:`zephyr:bluetooth_api` diff --git a/samples/bluetooth/channel_classification/prj.conf b/samples/bluetooth/channel_classification/prj.conf new file mode 100644 index 000000000000..b5651334c610 --- /dev/null +++ b/samples/bluetooth/channel_classification/prj.conf @@ -0,0 +1,24 @@ +# +# Copyright (c) 2026 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +CONFIG_NCS_SAMPLES_DEFAULTS=y + +CONFIG_CONSOLE=y +CONFIG_CONSOLE_SUBSYS=y +CONFIG_CONSOLE_HANDLER=y +CONFIG_CONSOLE_GETCHAR=y + +CONFIG_BT_DEVICE_NAME="Nordic_Channel_Class" +CONFIG_BT=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_MAX_CONN=1 + +CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION=y +CONFIG_BT_HCI_VS_EVT_USER=y + +CONFIG_BT_SCAN=y +CONFIG_BT_SCAN_FILTER_ENABLE=y +CONFIG_BT_SCAN_NAME_CNT=1 diff --git a/samples/bluetooth/channel_classification/src/main.c b/samples/bluetooth/channel_classification/src/main.c new file mode 100644 index 000000000000..5c6202f86529 --- /dev/null +++ b/samples/bluetooth/channel_classification/src/main.c @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(app_main, LOG_LEVEL_INF); + +#define DATA_CHANNEL_COUNT 37 +#define CHANNEL_CLASSIFICATION_SIZE 10 +#define CHANNEL_MAP_COUNT 3 +#define CHANNEL_MAP_UPDATE_PERIOD K_SECONDS(5) +#define REPORTING_MIN_SPACING 5U +#define REPORTING_MAX_DELAY 5U + +static struct bt_conn *default_conn; +static uint8_t device_role; +static uint8_t channel_map_index; +static struct k_work_delayable channel_map_update_work; + +static const uint8_t channel_maps[CHANNEL_MAP_COUNT][5] = { + {0xFF, 0xFF, 0xFF, 0xFF, 0x1F}, + {0xFF, 0xFB, 0xEF, 0xBF, 0x1F}, + {0xDF, 0x7F, 0xFF, 0xFD, 0x1F}, +}; + +static const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), +}; + +static const struct bt_le_conn_param conn_param = { + .interval_min = 0x0018, + .interval_max = 0x0018, + .latency = 0, + .timeout = 400, +}; + +static void channel_map_set_bit(uint8_t map[5], uint8_t channel, bool good) +{ + if (channel >= DATA_CHANNEL_COUNT) { + return; + } + + if (good) { + map[channel / 8] |= BIT(channel % 8); + } else { + map[channel / 8] &= ~BIT(channel % 8); + } +} + +static void channel_map_print(const char *prefix, const uint8_t map[5]) +{ + LOG_INF("%s %02x %02x %02x %02x %02x", prefix, map[0], map[1], map[2], map[3], + map[4] & 0x1F); +} + +static void +classification_to_channel_map(const uint8_t classification[CHANNEL_CLASSIFICATION_SIZE], + uint8_t map[5]) +{ + memset(map, 0, sizeof(map[0]) * 5); + + for (uint8_t channel = 0; channel < DATA_CHANNEL_COUNT; channel++) { + const uint8_t byte_index = (channel * 2U) / 8U; + const uint8_t bit_offset = (channel * 2U) % 8U; + const uint8_t value = (classification[byte_index] >> bit_offset) & 0x03U; + const bool good = (value == 0x01U); + + channel_map_set_bit(map, channel, good); + } + + map[4] &= 0x1F; +} + +static void peripheral_channel_map_update(struct k_work *work) +{ + uint8_t map[5]; + int err; + + if (!default_conn) { + return; + } + + memcpy(map, channel_maps[channel_map_index], sizeof(map)); + + LOG_INF("Using channel map %u", channel_map_index); + channel_map_print("Peripheral channel map", map); + + err = bt_le_set_chan_map(map); + if (err) { + LOG_WRN("bt_le_set_chan_map failed (err %d)", err); + } + + channel_map_index = (channel_map_index + 1U) % CHANNEL_MAP_COUNT; + + k_work_schedule(k_work_delayable_from_work(work), CHANNEL_MAP_UPDATE_PERIOD); +} + +static bool on_vs_evt(struct net_buf_simple *buf) +{ + uint8_t subevent_code; + + subevent_code = net_buf_simple_pull_u8(buf); + + switch (subevent_code) { + case SDC_HCI_SUBEVENT_VS_CHANNEL_CLASSIFICATION_REPORT: { + sdc_hci_subevent_vs_channel_classification_report_t *evt = (void *)buf->data; + struct bt_conn *conn; + uint8_t map[5]; + int err; + + conn = bt_hci_conn_lookup_handle(evt->conn_handle); + if (!conn) { + return true; + } + + classification_to_channel_map(evt->channel_classification, map); + channel_map_print("Central channel map from report", map); + + err = bt_le_set_chan_map(map); + if (err) { + LOG_WRN("Central bt_le_set_chan_map failed (err %d)", err); + } else { + LOG_INF("Central applied peripheral channel classification"); + } + + bt_conn_unref(conn); + return true; + } + case SDC_HCI_SUBEVENT_VS_CHANNEL_REPORTING_ENABLE_COMPLETE: { + sdc_hci_subevent_vs_channel_reporting_enable_complete_t *evt = (void *)buf->data; + + if (evt->status == BT_HCI_ERR_SUCCESS) { + LOG_INF("Channel classification reporting enabled"); + } else { + LOG_WRN("Channel classification reporting enable failed: 0x%02x %s", + evt->status, bt_hci_err_to_str(evt->status)); + } + return true; + } + default: + return false; + } +} + +static int central_reporting_start(struct bt_conn *conn) +{ + sdc_hci_cmd_vs_channel_reporting_enable_t params; + uint16_t conn_handle; + int err; + + err = bt_hci_get_conn_handle(conn, &conn_handle); + if (err) { + return err; + } + + params.conn_handle = conn_handle; + params.enable = true; + params.min_spacing = REPORTING_MIN_SPACING; + params.max_delay = REPORTING_MAX_DELAY; + + return hci_vs_sdc_channel_reporting_enable(¶ms); +} + +static void scan_filter_match(struct bt_scan_device_info *device_info, + struct bt_scan_filter_match *filter_match, bool connectable) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr)); + LOG_INF("Filters matched. Address: %s connectable: %d", addr, connectable); +} + +static void scan_connecting_error(struct bt_scan_device_info *device_info) +{ + LOG_WRN("Connection failed"); +} + +BT_SCAN_CB_INIT(scan_cb, scan_filter_match, NULL, scan_connecting_error, NULL); + +static void scan_init(void) +{ + int err; + struct bt_le_scan_param scan_param = { + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE, + .interval = 0x0010, + .window = 0x0010, + }; + struct bt_scan_init_param scan_init = { + .connect_if_match = true, + .scan_param = &scan_param, + .conn_param = &conn_param, + }; + + bt_scan_init(&scan_init); + bt_scan_cb_register(&scan_cb); + + err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_NAME, CONFIG_BT_DEVICE_NAME); + if (err) { + LOG_WRN("Scanning filters cannot be set (err %d)", err); + return; + } + + err = bt_scan_filter_enable(BT_SCAN_NAME_FILTER, false); + if (err) { + LOG_WRN("Filters cannot be turned on (err %d)", err); + } +} + +static void scan_start(void) +{ + int err; + + err = bt_scan_start(BT_SCAN_TYPE_SCAN_PASSIVE); + if (err) { + LOG_WRN("Starting scanning failed (err %d)", err); + return; + } + + LOG_INF("Scanning successfully started"); +} + +static void adv_start(void) +{ + int err; + const struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM( + BT_LE_ADV_OPT_CONN, BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL); + + err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), NULL, 0); + if (err) { + LOG_WRN("Advertising failed to start (err %d)", err); + return; + } + + LOG_INF("Advertising successfully started"); +} + +static void connected(struct bt_conn *conn, uint8_t err) +{ + struct bt_conn_info info; + + if (err) { + LOG_WRN("Connection failed, err 0x%02x %s", err, bt_hci_err_to_str(err)); + return; + } + + default_conn = bt_conn_ref(conn); + + err = bt_conn_get_info(default_conn, &info); + if (err) { + LOG_WRN("Getting conn info failed (err %d)", err); + return; + } + + device_role = info.role; + + if (info.role == BT_CONN_ROLE_CENTRAL) { + bt_scan_stop(); + } else { + bt_le_adv_stop(); + } + + LOG_INF("Connected as %s", info.role == BT_CONN_ROLE_CENTRAL ? "central" : "peripheral"); + + if (info.role == BT_CONN_ROLE_CENTRAL) { + err = central_reporting_start(default_conn); + if (err) { + LOG_WRN("Failed to start channel classification reporting (err %d)", err); + } + } else { + channel_map_index = 0; + k_work_schedule(&channel_map_update_work, CHANNEL_MAP_UPDATE_PERIOD); + } +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + LOG_INF("Disconnected, reason 0x%02x %s", reason, bt_hci_err_to_str(reason)); + + k_work_cancel_delayable(&channel_map_update_work); + channel_map_index = 0; + + if (default_conn) { + bt_conn_unref(default_conn); + default_conn = NULL; + } + + if (device_role == BT_CONN_ROLE_CENTRAL) { + scan_start(); + } else { + adv_start(); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, +}; + +int main(void) +{ + int err; + + console_init(); + + LOG_INF("Starting Bluetooth Channel Classification sample"); + + k_work_init_delayable(&channel_map_update_work, peripheral_channel_map_update); + + err = bt_enable(NULL); + if (err) { + LOG_WRN("Bluetooth init failed (err %d)", err); + return 0; + } + + LOG_INF("Bluetooth initialized"); + + while (true) { + LOG_INF("Choose device role - type c (central) or p (peripheral): "); + + const char input_char = console_getchar(); + + LOG_INF(""); + + if (input_char == 'c') { + LOG_INF("Selected Central"); + + err = bt_hci_register_vnd_evt_cb(on_vs_evt); + if (err) { + LOG_WRN("Failed to register HCI VS callback (err %d)", err); + continue; + } + + scan_init(); + scan_start(); + break; + } + + if (input_char == 'p') { + LOG_INF("Selected Peripheral"); + adv_start(); + break; + } + + LOG_INF("Invalid role"); + } + + return 0; +} diff --git a/samples/bluetooth/channel_classification/sysbuild/ipc_radio/prj.conf b/samples/bluetooth/channel_classification/sysbuild/ipc_radio/prj.conf new file mode 100644 index 000000000000..06ccf7e0e9bb --- /dev/null +++ b/samples/bluetooth/channel_classification/sysbuild/ipc_radio/prj.conf @@ -0,0 +1,10 @@ +# +# Copyright (c) 2026 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_CTLR_CHANNEL_CLASSIFICATION=y +CONFIG_BT_MAX_CONN=1 diff --git a/samples/bluetooth/channel_classification/tests.yaml b/samples/bluetooth/channel_classification/tests.yaml new file mode 100644 index 000000000000..077d6a6ccd54 --- /dev/null +++ b/samples/bluetooth/channel_classification/tests.yaml @@ -0,0 +1,28 @@ +sample: + description: Bluetooth Low Energy Channel Classification sample + name: Bluetooth LE Channel Classification +tests: + sample.bluetooth.channel_classification: + sysbuild: true + integration_platforms: + - nrf52840dk/nrf52840 + - nrf5340dk/nrf5340/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + platform_allow: + - nrf52840dk/nrf52840 + - nrf5340dk/nrf5340/cpuapp + - nrf5340dk/nrf5340/cpuapp/ns + - nrf54l15dk/nrf54l15/cpuapp + tags: + - bluetooth + - ci_build + - sysbuild + - ci_samples_bluetooth + harness: console + harness_config: + type: multi_line + ordered: true + regex: + - "Starting Bluetooth Channel Classification sample" + - "Bluetooth initialized" + timeout: 15