From 98e38a5d2aa2a72a31f28cc55c6fabfa4794f5d2 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Mon, 22 May 2023 21:03:08 +0530 Subject: [PATCH 01/21] NimBLE: Exposed macros used by GAP service to menuconfig --- components/bt/host/nimble/Kconfig.in | 158 ++++++++++++++++++ .../host/nimble/port/include/esp_nimble_cfg.h | 33 +++- 2 files changed, 184 insertions(+), 7 deletions(-) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 9937738544..38305f63a8 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -708,3 +708,161 @@ config BT_NIMBLE_HOST_ALLOW_CONNECT_WITH_SCAN depends on BT_NIMBLE_ENABLED && (IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S3) help This enables support for user to initiate a new connection with scan in progress + +menu "GAP Service" + menu "GAP Appearance write permissions" + config BT_NIMBLE_SVC_GAP_APPEAR_WRITE + bool "Write" + default n + help + Enable write permission (BLE_GATT_CHR_F_WRITE) + + config BT_NIMBLE_SVC_GAP_APPEAR_WRITE_ENC + depends on BT_NIMBLE_SVC_GAP_APPEAR_WRITE + bool "Write with encryption" + default n + help + Enable write with encryption permission (BLE_GATT_CHR_F_WRITE_ENC) + + config BT_NIMBLE_SVC_GAP_APPEAR_WRITE_AUTHEN + depends on BT_NIMBLE_SVC_GAP_APPEAR_WRITE + bool "Write with authentication" + default n + help + Enable write with authentication permission (BLE_GATT_CHR_F_WRITE_AUTHEN) + + config BT_NIMBLE_SVC_GAP_APPEAR_WRITE_AUTHOR + depends on BT_NIMBLE_SVC_GAP_APPEAR_WRITE + bool "Write with authorisation" + default n + help + Enable write with authorisation permission (BLE_GATT_CHR_F_WRITE_AUTHOR) + endmenu + + config BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM + int + default 0 if !BT_NIMBLE_SVC_GAP_APPEAR_WRITE + default 8 if BT_NIMBLE_SVC_GAP_APPEAR_WRITE + + config BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ENC + int + default 0 if !BT_NIMBLE_SVC_GAP_APPEAR_WRITE_ENC + default 4096 if BT_NIMBLE_SVC_GAP_APPEAR_WRITE_ENC + + config BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHN + int + default 0 if !BT_NIMBLE_SVC_GAP_APPEAR_WRITE_AUTHEN + default 8192 if BT_NIMBLE_SVC_GAP_APPEAR_WRITE_AUTHEN + + config BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHR + int + default 0 if !BT_NIMBLE_SVC_GAP_APPEAR_WRITE_AUTHOR + default 16384 if BT_NIMBLE_SVC_GAP_APPEAR_WRITE_AUTHOR + + choice BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION + prompt "GAP Characteristic - Central Address Resolution" + default BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP + help + Weather or not Central Address Resolution characteristic is supported on + the device, and if supported, weather or not Central Address Resolution + is supported. + + - Central Address Resolution characteristic not supported + - Central Address Resolution not supported + - Central Address Resolution supported + + config BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP + bool "Characteristic not supported" + + config BT_NIMBLE_SVC_GAP_CAR_NOT_SUPP + bool "Central Address Resolution not supported" + + config BT_NIMBLE_SVC_GAP_CAR_SUPP + bool "Central Address Resolution supported" + endchoice + + config BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION + int + default -1 if BT_NIMBLE_SVC_GAP_CAR_CHAR_NOT_SUPP + default 0 if BT_NIMBLE_SVC_GAP_CAR_NOT_SUPP + default 1 if BT_NIMBLE_SVC_GAP_CAR_SUPP + + menu "GAP device name write permissions" + config BT_NIMBLE_SVC_GAP_NAME_WRITE + bool "Write" + default n + help + Enable write permission (BLE_GATT_CHR_F_WRITE) + + config BT_NIMBLE_SVC_GAP_NAME_WRITE_ENC + depends on BT_NIMBLE_SVC_GAP_NAME_WRITE + bool "Write with encryption" + default n + help + Enable write with encryption permission (BLE_GATT_CHR_F_WRITE_ENC) + + config BT_NIMBLE_SVC_GAP_NAME_WRITE_AUTHEN + depends on BT_NIMBLE_SVC_GAP_NAME_WRITE + bool "Write with authentication" + default n + help + Enable write with authentication permission (BLE_GATT_CHR_F_WRITE_AUTHEN) + + config BT_NIMBLE_SVC_GAP_NAME_WRITE_AUTHOR + depends on BT_NIMBLE_SVC_GAP_NAME_WRITE + bool "Write with authorisation" + default n + help + Enable write with authorisation permission (BLE_GATT_CHR_F_WRITE_AUTHOR) + endmenu + + config BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM + int + default 0 if !BT_NIMBLE_SVC_GAP_NAME_WRITE + default 8 if BT_NIMBLE_SVC_GAP_NAME_WRITE + + config BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_ENC + int + default 0 if !BT_NIMBLE_SVC_GAP_NAME_WRITE_ENC + default 4096 if BT_NIMBLE_SVC_GAP_NAME_WRITE_ENC + + config BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHEN + int + default 0 if !BT_NIMBLE_SVC_GAP_NAME_WRITE_AUTHEN + default 8192 if BT_NIMBLE_SVC_GAP_NAME_WRITE_AUTHEN + + config BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHOR + int + default 0 if !BT_NIMBLE_SVC_GAP_NAME_WRITE_AUTHOR + default 16384 if BT_NIMBLE_SVC_GAP_NAME_WRITE_AUTHOR + + config BT_NIMBLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL + int "PPCP Connection Interval Max (Unit: 1.25 ms)" + depends on BT_NIMBLE_ROLE_PERIPHERAL + default 0 + help + Peripheral Preferred Connection Parameter: Connection Interval maximum value + Interval Max = value * 1.25 ms + + config BT_NIMBLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL + int "PPCP Connection Interval Min (Unit: 1.25 ms)" + depends on BT_NIMBLE_ROLE_PERIPHERAL + default 0 + help + Peripheral Preferred Connection Parameter: Connection Interval minimum value + Interval Min = value * 1.25 ms + + config BT_NIMBLE_SVC_GAP_PPCP_SLAVE_LATENCY + int "PPCP Slave Latency" + default 0 + help + Peripheral Preferred Connection Parameter: Slave Latency + + config BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO + int "PPCP Supervision Timeout (Uint: 10 ms)" + default 0 + help + Peripheral Preferred Connection Parameter: Supervision Timeout + Timeout = Value * 10 ms + +endmenu diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index dcba0f3c38..99198de023 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -1503,11 +1503,19 @@ #endif #ifndef MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE_WRITE_PERM +#if CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM +#define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE_WRITE_PERM ( \ + CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ENC | \ + CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHN | \ + CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM_ATHR) +#else #define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE_WRITE_PERM (-1) -#endif +#endif //CONFIG_BT_NIMBLE_SVC_GAP_APPEAR_WRITE_PERM +#endif //MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE_WRITE_PERM #ifndef MYNEWT_VAL_BLE_SVC_GAP_CENTRAL_ADDRESS_RESOLUTION -#define MYNEWT_VAL_BLE_SVC_GAP_CENTRAL_ADDRESS_RESOLUTION (-1) +#define MYNEWT_VAL_BLE_SVC_GAP_CENTRAL_ADDRESS_RESOLUTION \ + CONFIG_BT_NIMBLE_SVC_GAP_CENT_ADDR_RESOLUTION #endif #ifndef CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME @@ -1521,23 +1529,34 @@ #endif #ifndef MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_WRITE_PERM +#if CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM +#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_WRITE_PERM ( \ + CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_ENC | \ + CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHEN | \ + CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM_AUTHOR) +#else #define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_WRITE_PERM (-1) -#endif +#endif //CONFIG_BT_NIMBLE_SVC_GAP_NAME_WRITE_PERM +#endif //MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_WRITE_PERM #ifndef MYNEWT_VAL_BLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL -#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL (0) +#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL \ + CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL #endif #ifndef MYNEWT_VAL_BLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL -#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL (0) +#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL \ + CONFIG_BT_NIMBLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL #endif #ifndef MYNEWT_VAL_BLE_SVC_GAP_PPCP_SLAVE_LATENCY -#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SLAVE_LATENCY (0) +#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SLAVE_LATENCY \ + CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SLAVE_LATENCY #endif #ifndef MYNEWT_VAL_BLE_SVC_GAP_PPCP_SUPERVISION_TMO -#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SUPERVISION_TMO (0) +#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SUPERVISION_TMO \ + CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO #endif /*** nimble/transport */ From 404c548ad136affa4935155eacd0834c116c643b Mon Sep 17 00:00:00 2001 From: SumeetSingh19 Date: Thu, 18 Jan 2024 17:05:52 +0530 Subject: [PATCH 02/21] fix(nimble): allow auto connection and observer role --- components/bt/host/nimble/Kconfig.in | 11 +++++++++++ components/bt/host/nimble/nimble | 2 +- .../bt/host/nimble/port/include/esp_nimble_cfg.h | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 38305f63a8..668895ae13 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -210,6 +210,17 @@ config BT_NIMBLE_LL_CFG_FEAT_LE_ENCRYPTION help Enable encryption connection +config BT_NIMBLE_SM_SC_LVL + int "Security level" + depends on BT_NIMBLE_SECURITY_ENABLE + default 0 + help + LE Security Mode 1 Levels: + 1. No Security + 2. Unauthenticated pairing with encryption + 3. Authenticated pairing with encryption + 4. Authenticated LE Secure Connections pairing with encryption using a 128-bit strength encryption key. + config BT_NIMBLE_DEBUG bool "Enable extra runtime asserts and host debugging" default n diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index 1cb8108e15..dfdcf0ba53 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit 1cb8108e15303a5d6b9e5b04f3a65877dcb63ed3 +Subproject commit dfdcf0ba53c908d5a2f0d0f5848f6acf4799930f diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 99198de023..6b88751a43 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -820,6 +820,10 @@ #endif #endif +#ifndef MYNEWT_VAL_BLE_SM_SC_LVL +#define MYNEWT_VAL_BLE_SM_SC_LVL CONFIG_BT_NIMBLE_SM_SC_LVL +#endif + #ifndef MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST #define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (0) #endif From 2031366e54e8cf630018da0580079f09cf327b05 Mon Sep 17 00:00:00 2001 From: SumeetSingh19 Date: Thu, 18 Jan 2024 17:07:05 +0530 Subject: [PATCH 03/21] feat(nimble): authorization permission on gatt read and write --- examples/bluetooth/nimble/bleprph/main/main.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/examples/bluetooth/nimble/bleprph/main/main.c b/examples/bluetooth/nimble/bleprph/main/main.c index 5298bcad1e..b1a5800a14 100644 --- a/examples/bluetooth/nimble/bleprph/main/main.c +++ b/examples/bluetooth/nimble/bleprph/main/main.c @@ -395,6 +395,16 @@ bleprph_gap_event(struct ble_gap_event *event, void *arg) } return 0; + case BLE_GAP_EVENT_AUTHORIZE: + MODLOG_DFLT(INFO, "authorize event: conn_handle=%d attr_handle=%d is_read=%d", + event->authorize.conn_handle, + event->authorize.attr_handle, + event->authorize.is_read); + + /* The default behaviour for the event is to reject authorize request */ + event->authorize.out_response = BLE_GAP_AUTHORIZE_REJECT; + return 0; + #if MYNEWT_VAL(BLE_POWER_CONTROL) case BLE_GAP_EVENT_TRANSMIT_POWER: MODLOG_DFLT(INFO, "Transmit power event : status=%d conn_handle=%d reason=%d " From ba6a2dbb42bb789d15d21c32d6941eb088190232 Mon Sep 17 00:00:00 2001 From: SumeetSingh19 Date: Mon, 22 Jan 2024 12:43:44 +0530 Subject: [PATCH 04/21] feat(nimble): signed write support --- components/bt/porting/nimble/include/nimble/nimble_opt_auto.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/bt/porting/nimble/include/nimble/nimble_opt_auto.h b/components/bt/porting/nimble/include/nimble/nimble_opt_auto.h index 7aeafcc321..daf2153348 100644 --- a/components/bt/porting/nimble/include/nimble/nimble_opt_auto.h +++ b/components/bt/porting/nimble/include/nimble/nimble_opt_auto.h @@ -92,6 +92,10 @@ extern "C" { #define NIMBLE_BLE_ATT_CLT_WRITE \ (MYNEWT_VAL(BLE_GATT_WRITE)) +#undef NIMBLE_BLE_ATT_CLT_SIGNED_WRITE +#define NIMBLE_BLE_ATT_CLT_SIGNED_WRITE \ + (MYNEWT_VAL(BLE_GATT_SIGNED_WRITE)) + #undef NIMBLE_BLE_ATT_CLT_WRITE_NO_RSP #define NIMBLE_BLE_ATT_CLT_WRITE_NO_RSP \ (MYNEWT_VAL(BLE_GATT_WRITE_NO_RSP)) From 5c7ae46538785768a488b35b5e00498c67aec319 Mon Sep 17 00:00:00 2001 From: Roshan Bangar Date: Tue, 14 Mar 2023 14:28:16 +0530 Subject: [PATCH 05/21] feat(nimble): Added support for dynamic services --- components/bt/host/nimble/Kconfig.in | 7 + .../host/nimble/port/include/esp_nimble_cfg.h | 5 + examples/bluetooth/.build-test-rules.yml | 6 + .../nimble/ble_dynamic_service/CMakeLists.txt | 8 + .../nimble/ble_dynamic_service/README.md | 167 ++++++++++ .../ble_dynamic_service/main/CMakeLists.txt | 5 + .../main/ble_dynamic_service.h | 36 +++ .../ble_dynamic_service/main/gatt_svr.c | 264 +++++++++++++++ .../nimble/ble_dynamic_service/main/main.c | 304 ++++++++++++++++++ .../ble_dynamic_service/sdkconfig.defaults | 13 + ...Ble_Dynamic_Service_Example_Walkthrough.md | 193 +++++++++++ 11 files changed, 1008 insertions(+) create mode 100644 examples/bluetooth/nimble/ble_dynamic_service/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_dynamic_service/README.md create mode 100644 examples/bluetooth/nimble/ble_dynamic_service/main/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_dynamic_service/main/ble_dynamic_service.h create mode 100644 examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c create mode 100644 examples/bluetooth/nimble/ble_dynamic_service/main/main.c create mode 100644 examples/bluetooth/nimble/ble_dynamic_service/sdkconfig.defaults create mode 100644 examples/bluetooth/nimble/ble_dynamic_service/tutorial/Ble_Dynamic_Service_Example_Walkthrough.md diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 668895ae13..11e916ecec 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -228,6 +228,13 @@ config BT_NIMBLE_DEBUG help This enables extra runtime asserts and host debugging +config BT_NIMBLE_DYNAMIC_SERVICE + bool "Enable dynamic services" + depends on BT_NIMBLE_ENABLED + help + This enables user to add/remove Gatt services at runtime + + config BT_NIMBLE_SVC_GAP_DEVICE_NAME string "BLE GAP default device name" depends on BT_NIMBLE_ENABLED diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 6b88751a43..0a6d4812a5 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -468,6 +468,11 @@ #endif /*** @apache-mynewt-nimble/nimble/host */ + +#ifndef MYNEWT_VAL_BLE_DYNAMIC_SERVICE +#define MYNEWT_VAL_BLE_DYNAMIC_SERVICE CONFIG_BT_NIMBLE_DYNAMIC_SERVICE +#endif + #ifndef MYNEWT_VAL_BLE_ATT_PREFERRED_MTU #define MYNEWT_VAL_BLE_ATT_PREFERRED_MTU CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU #endif diff --git a/examples/bluetooth/.build-test-rules.yml b/examples/bluetooth/.build-test-rules.yml index 044c764f38..fb873f686c 100644 --- a/examples/bluetooth/.build-test-rules.yml +++ b/examples/bluetooth/.build-test-rules.yml @@ -99,6 +99,12 @@ examples/bluetooth/nimble: reason: not tested yet +examples/bluetooth/nimble/ble_dynamic_service: + enable: + - if: IDF_TARGET in ["esp32", "esp32c2", "esp32c3", "esp32s3"] + temporary: true + reason: the other targets are not tested yet + examples/bluetooth/nimble/ble_enc_adv_data: enable: - if: SOC_BLE_SUPPORTED == 1 and IDF_TARGET != "esp32" diff --git a/examples/bluetooth/nimble/ble_dynamic_service/CMakeLists.txt b/examples/bluetooth/nimble/ble_dynamic_service/CMakeLists.txt new file mode 100644 index 0000000000..28cd09e537 --- /dev/null +++ b/examples/bluetooth/nimble/ble_dynamic_service/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_dynamic_service) diff --git a/examples/bluetooth/nimble/ble_dynamic_service/README.md b/examples/bluetooth/nimble/ble_dynamic_service/README.md new file mode 100644 index 0000000000..d639b8fa51 --- /dev/null +++ b/examples/bluetooth/nimble/ble_dynamic_service/README.md @@ -0,0 +1,167 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# BLE Dyanamic Service Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example creates GATT server and then starts advertising, waiting to be connected to a GATT client. +In the main thread it keeps on adding and deleting one custom service in the gatt server. + +It uses ESP32's Bluetooth controller and NimBLE stack based BLE host. + +This example aims at understanding addition and deletion of services at runtime. +Note : Services can be added at the time of init. This example focuses on adding services after stack init. There may be active connections at the time the new service is added/deleted. + +To test this demo, any BLE scanner app can be used. + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Configure the project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +There is this console output when ble_dynamic_service is connected to ble scanner: + +``` +I (354) BLE_INIT: BT controller compile version [80abacd] +I (354) phy_init: phy_version 950,f732b06,Feb 15 2023,18:57:12 +I (404) BLE_INIT: Bluetooth MAC: 58:cf:79:e9:c1:9e + +I (404) NimBLE_BLE_DYNAMIC_SERVER: BLE Host Task Started +I (404) NimBLE: GAP procedure initiated: stop advertising. + +I (414) NimBLE: Failed to restore IRKs from store; status=8 + +I (414) NimBLE: Device Address: +I (424) NimBLE: 58:cf:79:e9:c1:9e +I (424) NimBLE: + +I (424) NimBLE: GAP procedure initiated: advertise; +I (434) NimBLE: disc_mode=2 +I (434) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0 +I (444) NimBLE: + +I (15444) NimBLE: Adding Dynamic service +I (30444) NimBLE: Deleting service +I (45444) NimBLE: Adding Dynamic service +I (60444) NimBLE: Deleting service +I (75444) NimBLE: Adding Dynamic service +I (90444) NimBLE: Deleting service +I (101654) NimBLE: connection established; status=0 +I (101654) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr= +I (101654) NimBLE: 58:cf:79:e9:c1:9e +I (101664) NimBLE: our_id_addr_type=0 our_id_addr= +I (101664) NimBLE: 58:cf:79:e9:c1:9e +I (101674) NimBLE: peer_ota_addr_type=1 peer_ota_addr= +I (101674) NimBLE: 41:c7:05:e9:d9:ce +I (101684) NimBLE: peer_id_addr_type=1 peer_id_addr= +I (101684) NimBLE: 41:c7:05:e9:d9:ce +I (101694) NimBLE: conn_itvl=39 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0 + +I (101704) NimBLE: + +I (102284) NimBLE: connection updated; status=0 +I (102284) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr= +I (102284) NimBLE: 58:cf:79:e9:c1:9e +I (102294) NimBLE: our_id_addr_type=0 our_id_addr= +I (102294) NimBLE: 58:cf:79:e9:c1:9e +I (102304) NimBLE: peer_ota_addr_type=1 peer_ota_addr= +I (102304) NimBLE: 41:c7:05:e9:d9:ce +I (102314) NimBLE: peer_id_addr_type=1 peer_id_addr= +I (102314) NimBLE: 41:c7:05:e9:d9:ce +I (102324) NimBLE: conn_itvl=6 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0 + +I (102334) NimBLE: + +I (102584) NimBLE: connection updated; status=0 +I (102584) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr= +I (102584) NimBLE: 58:cf:79:e9:c1:9e +I (102584) NimBLE: our_id_addr_type=0 our_id_addr= +I (102594) NimBLE: 58:cf:79:e9:c1:9e +I (102594) NimBLE: peer_ota_addr_type=1 peer_ota_addr= +I (102604) NimBLE: 41:c7:05:e9:d9:ce +I (102604) NimBLE: peer_id_addr_type=1 peer_id_addr= +I (102614) NimBLE: 41:c7:05:e9:d9:ce +I (102614) NimBLE: conn_itvl=39 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0 + +I (102624) NimBLE: + +I (104974) NimBLE: subscribe event; conn_handle=1 attr_handle=8 reason=1 prevn=0 curn=0 previ=0 curi=1 + +I (105444) NimBLE: Adding Dynamic service +I (105444) NimBLE: GATT procedure initiated: indicate; +I (105444) NimBLE: att_handle=8 + +I (105444) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=0 is_indication=1 +I (105554) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=14 is_indication=1 +I (105554) NimBLE: GATT procedure initiated: indicate; +I (105564) NimBLE: att_handle=8 + +I (105564) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=0 is_indication=1 +I (105654) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=14 is_indication=1 +I (105904) NimBLE: connection updated; status=0 +I (105904) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr= +I (105904) NimBLE: 58:cf:79:e9:c1:9e +I (105904) NimBLE: our_id_addr_type=0 our_id_addr= +I (105914) NimBLE: 58:cf:79:e9:c1:9e +I (105914) NimBLE: peer_ota_addr_type=1 peer_ota_addr= +I (105924) NimBLE: 41:c7:05:e9:d9:ce +I (105924) NimBLE: peer_id_addr_type=1 peer_id_addr= +I (105934) NimBLE: 41:c7:05:e9:d9:ce +I (105934) NimBLE: conn_itvl=6 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0 + +I (105944) NimBLE: + +I (106334) NimBLE: connection updated; status=0 +I (106334) NimBLE: handle=1 our_ota_addr_type=0 our_ota_addr= +I (106334) NimBLE: 58:cf:79:e9:c1:9e +I (106344) NimBLE: our_id_addr_type=0 our_id_addr= +I (106344) NimBLE: 58:cf:79:e9:c1:9e +I (106354) NimBLE: peer_ota_addr_type=1 peer_ota_addr= +I (106354) NimBLE: 41:c7:05:e9:d9:ce +I (106364) NimBLE: peer_id_addr_type=1 peer_id_addr= +I (106364) NimBLE: 41:c7:05:e9:d9:ce +I (106374) NimBLE: conn_itvl=39 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0 + +I (106384) NimBLE: + +I (109604) NimBLE: subscribe event; conn_handle=1 attr_handle=40 reason=1 prevn=0 curn=1 previ=0 curi=0 + +I (110484) NimBLE: subscribe event; conn_handle=1 attr_handle=40 reason=1 prevn=1 curn=0 previ=0 curi=0 + +I (112484) NimBLE: Characteristic read; conn_handle=1 attr_handle=40 + +I (120454) NimBLE: Deleting service +I (120454) NimBLE: GATT procedure initiated: indicate; +I (120454) NimBLE: att_handle=8 + +I (120454) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=0 is_indication=1 +I (120574) NimBLE: notify_tx event; conn_handle=1 attr_handle=8 status=14 is_indication=1 +I (120574) NimBLE: GATT procedure initiated: indicate; +I (120574) NimBLE: att_handle=8 + +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/ble_dynamic_service/main/CMakeLists.txt b/examples/bluetooth/nimble/ble_dynamic_service/main/CMakeLists.txt new file mode 100644 index 0000000000..9e539a9fc0 --- /dev/null +++ b/examples/bluetooth/nimble/ble_dynamic_service/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(srcs "main.c" + "gatt_svr.c") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/ble_dynamic_service/main/ble_dynamic_service.h b/examples/bluetooth/nimble/ble_dynamic_service/main/ble_dynamic_service.h new file mode 100644 index 0000000000..dcd2854ca5 --- /dev/null +++ b/examples/bluetooth/nimble/ble_dynamic_service/main/ble_dynamic_service.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef H_BLE_DYNAMIC_SERVICE_ +#define H_BLE_DYNAMIC_SERVICE_ + +#include +#include "nimble/ble.h" +#include "modlog/modlog.h" +#include "esp_peripheral.h" +#ifdef __cplusplus +extern "C" { +#endif + +struct ble_hs_cfg; +struct ble_gatt_register_ctxt; + +/** GATT server. */ +#define GATT_SVR_SVC_ALERT_UUID 0x1811 +#define GATT_SVR_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47 +#define GATT_SVR_CHR_NEW_ALERT 0x2A46 +#define GATT_SVR_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48 +#define GATT_SVR_CHR_UNR_ALERT_STAT_UUID 0x2A45 +#define GATT_SVR_CHR_ALERT_NOT_CTRL_PT 0x2A44 + +void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg); +int gatt_svr_init(void); +int dynamic_service(const uint8_t operation, const struct ble_gatt_svc_def *svcs, const ble_uuid_t *uuid); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c b/examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c new file mode 100644 index 0000000000..f6afece49c --- /dev/null +++ b/examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#include "ble_dynamic_service.h" +#include "services/ans/ble_svc_ans.h" + +/*** Maximum number of characteristics with the notify flag ***/ +#define MAX_NOTIFY 5 + +static const ble_uuid128_t gatt_svr_svc_uuid = + BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12, + 0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59); + +/* A characteristic that can be subscribed to */ +static uint8_t gatt_svr_chr_val; +static uint16_t gatt_svr_chr_val_handle; +static const ble_uuid128_t gatt_svr_chr_uuid = + BLE_UUID128_INIT(0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, + 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33); + +/* A custom descriptor */ +static uint8_t gatt_svr_dsc_val; +static const ble_uuid128_t gatt_svr_dsc_uuid = + BLE_UUID128_INIT(0x01, 0x01, 0x01, 0x01, 0x12, 0x12, 0x12, 0x12, + 0x23, 0x23, 0x23, 0x23, 0x34, 0x34, 0x34, 0x34); + +static int +gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, + void *arg); + +const struct ble_gatt_svc_def gatt_svr_svcs[] = { + { + /*** Service ***/ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &gatt_svr_svc_uuid.u, + .characteristics = (struct ble_gatt_chr_def[]) + { { + /*** This characteristic can be subscribed to by writing 0x00 and 0x01 to the CCCD ***/ + .uuid = &gatt_svr_chr_uuid.u, + .access_cb = gatt_svc_access, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE, + .val_handle = &gatt_svr_chr_val_handle, + .descriptors = (struct ble_gatt_dsc_def[]) + { { + .uuid = &gatt_svr_dsc_uuid.u, + .att_flags = BLE_ATT_F_READ, + .access_cb = gatt_svc_access, + }, { + 0, /* No more descriptors in this characteristic */ + } + }, + }, { + 0, /* No more characteristics in this service. */ + } + }, + }, + + { + 0, /* No more services. */ + }, +}; + +static int +gatt_svr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, + void *dst, uint16_t *len) +{ + uint16_t om_len; + int rc; + + om_len = OS_MBUF_PKTLEN(om); + if (om_len < min_len || om_len > max_len) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + rc = ble_hs_mbuf_to_flat(om, dst, max_len, len); + if (rc != 0) { + return BLE_ATT_ERR_UNLIKELY; + } + + return 0; +} + +/** + * Access callback whenever a characteristic/descriptor is read or written to. + * Here reads and writes need to be handled. + * ctxt->op tells weather the operation is read or write and + * weather it is on a characteristic or descriptor, + * ctxt->dsc->uuid tells which characteristic/descriptor is accessed. + * attr_handle give the value handle of the attribute being accessed. + * Accordingly do: + * Append the value to ctxt->om if the operation is READ + * Write ctxt->om to the value if the operation is WRITE + **/ +static int +gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + const ble_uuid_t *uuid; + int rc; + + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + MODLOG_DFLT(INFO, "Characteristic read; conn_handle=%d attr_handle=%d\n", + conn_handle, attr_handle); + } else { + MODLOG_DFLT(INFO, "Characteristic read by NimBLE stack; attr_handle=%d\n", + attr_handle); + } + uuid = ctxt->chr->uuid; + if (attr_handle == gatt_svr_chr_val_handle) { + rc = os_mbuf_append(ctxt->om, + &gatt_svr_chr_val, + sizeof(gatt_svr_chr_val)); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + goto unknown; + + case BLE_GATT_ACCESS_OP_WRITE_CHR: + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + MODLOG_DFLT(INFO, "Characteristic write; conn_handle=%d attr_handle=%d", + conn_handle, attr_handle); + } else { + MODLOG_DFLT(INFO, "Characteristic write by NimBLE stack; attr_handle=%d", + attr_handle); + } + uuid = ctxt->chr->uuid; + if (attr_handle == gatt_svr_chr_val_handle) { + rc = gatt_svr_write(ctxt->om, + sizeof(gatt_svr_chr_val), + sizeof(gatt_svr_chr_val), + &gatt_svr_chr_val, NULL); + ble_gatts_chr_updated(attr_handle); + MODLOG_DFLT(INFO, "Notification/Indication scheduled for " + "all subscribed peers.\n"); + return rc; + } + goto unknown; + + case BLE_GATT_ACCESS_OP_READ_DSC: + if (conn_handle != BLE_HS_CONN_HANDLE_NONE) { + MODLOG_DFLT(INFO, "Descriptor read; conn_handle=%d attr_handle=%d\n", + conn_handle, attr_handle); + } else { + MODLOG_DFLT(INFO, "Descriptor read by NimBLE stack; attr_handle=%d\n", + attr_handle); + } + uuid = ctxt->dsc->uuid; + if (ble_uuid_cmp(uuid, &gatt_svr_dsc_uuid.u) == 0) { + rc = os_mbuf_append(ctxt->om, + &gatt_svr_dsc_val, + sizeof(gatt_svr_chr_val)); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + goto unknown; + + case BLE_GATT_ACCESS_OP_WRITE_DSC: + goto unknown; + + default: + goto unknown; + } + +unknown: + /* Unknown characteristic/descriptor; + * The NimBLE host should not have called this function; + */ + assert(0); + return BLE_ATT_ERR_UNLIKELY; +} + +void +gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) +{ + char buf[BLE_UUID_STR_LEN]; + + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n", + ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), + ctxt->svc.handle); + break; + + case BLE_GATT_REGISTER_OP_CHR: + MODLOG_DFLT(DEBUG, "registering characteristic %s with " + "def_handle=%d val_handle=%d\n", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), + ctxt->chr.def_handle, + ctxt->chr.val_handle); + break; + + case BLE_GATT_REGISTER_OP_DSC: + MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n", + ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), + ctxt->dsc.handle); + break; + + default: + assert(0); + break; + } +} + +/* Adds custom service + *@params + *operation : uint8_t (1 for add and 0 for delete) + *gatt_svr_svcs : struct ble_gatt_svc_def (NULL if operation is delete, else pass the services) + *uuid : const ble_uuid_t * (NULL if operation is add, else pass the uuid of the service to be deleted) + */ +int dynamic_service(const uint8_t operation, const struct ble_gatt_svc_def *svcs, const ble_uuid_t *uuid) { + int rc = 0; + int i = 0; + switch(operation) { + case 1: + /* add services in gatt_svr_svcs */ + if(svcs == NULL) { + /* don't add anything gatt_svr_svcs is NULL */ + MODLOG_DFLT(ERROR, "No services to add\n"); + return ESP_FAIL; + } + rc = ble_gatts_add_dynamic_svcs(svcs); + return rc; + break; + case 0: + /* delete service by uuid*/ + if(uuid == NULL) { + MODLOG_DFLT(ERROR, "No service to delete\n"); + return ESP_FAIL; + } + rc = ble_gatts_delete_svc(uuid); + if(rc != 0) { + /* not able to delete service return immidietely */ + return rc; + } + i++; + return rc; + break; + } + return rc; +} + +int +gatt_svr_init(void) +{ + ble_svc_gap_init(); + ble_svc_gatt_init(); + ble_svc_ans_init(); + + /* Setting a value for the read-only descriptor */ + gatt_svr_dsc_val = 0x99; + + return 0; +} diff --git a/examples/bluetooth/nimble/ble_dynamic_service/main/main.c b/examples/bluetooth/nimble/ble_dynamic_service/main/main.c new file mode 100644 index 0000000000..37186c9a4b --- /dev/null +++ b/examples/bluetooth/nimble/ble_dynamic_service/main/main.c @@ -0,0 +1,304 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#include "services/gap/ble_svc_gap.h" +#include "ble_dynamic_service.h" + +#define BLE_DYNAMIC_SERVICE_ADD 1 +#define BLE_DYNAMIC_SERVICE_DELETE 0 +extern const struct ble_gatt_svc_def gatt_svr_svcs[]; +static const char *tag = "NimBLE_DYNAMIC_SERVICE"; +static int dynamic_service_gap_event(struct ble_gap_event *event, void *arg); +static uint8_t own_addr_type; + +/** + * Logs information about a connection to the console. + */ +static void +dynamic_service_print_conn_desc(struct ble_gap_conn_desc *desc) +{ + MODLOG_DFLT(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=", + desc->conn_handle, desc->our_ota_addr.type); + print_addr(desc->our_ota_addr.val); + MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=", + desc->our_id_addr.type); + print_addr(desc->our_id_addr.val); + MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=", + desc->peer_ota_addr.type); + print_addr(desc->peer_ota_addr.val); + MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=", + desc->peer_id_addr.type); + print_addr(desc->peer_id_addr.val); + MODLOG_DFLT(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d " + "encrypted=%d authenticated=%d bonded=%d\n", + desc->conn_itvl, desc->conn_latency, + desc->supervision_timeout, + desc->sec_state.encrypted, + desc->sec_state.authenticated, + desc->sec_state.bonded); +} + +/** + * Enables advertising with the following parameters: + * o General discoverable mode. + * o Undirected connectable mode. + */ +static void +dynamic_service_advertise(void) +{ + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + const char *name; + int rc; + + /** + * Set the advertisement data included in our advertisements: + * o Flags (indicates advertisement type and other general info). + * o Advertising tx power. + * o Device name. + * o 16-bit service UUIDs (alert notifications). + */ + + memset(&fields, 0, sizeof fields); + + /* Advertise two flags: + * o Discoverability in forthcoming advertisement (general) + * o BLE-only (BR/EDR unsupported). + */ + fields.flags = BLE_HS_ADV_F_DISC_GEN | + BLE_HS_ADV_F_BREDR_UNSUP; + + /* Indicate that the TX power level field should be included; have the + * stack fill this value automatically. This is done by assigning the + * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. + */ + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + + name = ble_svc_gap_device_name(); + fields.name = (uint8_t *)name; + fields.name_len = strlen(name); + fields.name_is_complete = 1; + + fields.uuids16 = (ble_uuid16_t[]) { + BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID) + }; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc); + return; + } + + /* Begin advertising. */ + memset(&adv_params, 0, sizeof adv_params); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, + &adv_params, dynamic_service_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); + return; + } +} + +/** + * The nimble host executes this callback when a GAP event occurs. The + * application associates a GAP event callback with each connection that forms. + * dynamic_service uses the same callback for all connections. + * + * @param event The type of event being signalled. + * @param ctxt Various information pertaining to the event. + * @param arg Application-specified argument; unused by + * dynamic_service. + * + * @return 0 if the application successfully handled the + * event; nonzero on failure. The semantics + * of the return code is specific to the + * particular GAP event being signalled. + */ +static int +dynamic_service_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + MODLOG_DFLT(INFO, "connection %s; status=%d ", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + if (event->connect.status == 0) { + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + dynamic_service_print_conn_desc(&desc); + } + MODLOG_DFLT(INFO, "\n"); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising. */ + dynamic_service_advertise(); + } + + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); + dynamic_service_print_conn_desc(&event->disconnect.conn); + MODLOG_DFLT(INFO, "\n"); + + /* Connection terminated; resume advertising. */ + dynamic_service_advertise(); + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + MODLOG_DFLT(INFO, "connection updated; status=%d ", + event->conn_update.status); + rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc); + assert(rc == 0); + dynamic_service_print_conn_desc(&desc); + MODLOG_DFLT(INFO, "\n"); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + MODLOG_DFLT(INFO, "advertise complete; reason=%d", + event->adv_complete.reason); + dynamic_service_advertise(); + return 0; + + case BLE_GAP_EVENT_NOTIFY_TX: + MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d " + "status=%d is_indication=%d", + event->notify_tx.conn_handle, + event->notify_tx.attr_handle, + event->notify_tx.status, + event->notify_tx.indication); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, + event->subscribe.attr_handle, + event->subscribe.reason, + event->subscribe.prev_notify, + event->subscribe.cur_notify, + event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + return 0; + + case BLE_GAP_EVENT_MTU: + MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + } + + return 0; +} + +static void +dynamic_service_on_reset(int reason) +{ + MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); +} + + +static void +dynamic_service_on_sync(void) +{ + int rc; + + /* Make sure we have proper identity address set (public preferred) */ + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc); + return; + } + + /* Printing ADDR */ + uint8_t addr_val[6] = {0}; + rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); + + MODLOG_DFLT(INFO, "Device Address: "); + print_addr(addr_val); + MODLOG_DFLT(INFO, "\n"); + /* Begin advertising. */ + dynamic_service_advertise(); +} + +void dynamic_service_host_task(void *param) +{ + ESP_LOGI(tag, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +void +app_main(void) +{ + int rc; + + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(tag, "Failed to init nimble %d ", ret); + return; + } + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = dynamic_service_on_reset; + ble_hs_cfg.sync_cb = dynamic_service_on_sync; + ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + rc = gatt_svr_init(); + assert(rc == 0); + + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("ble-dynamic-service"); + assert(rc == 0); + + nimble_port_freertos_init(dynamic_service_host_task); + + while(1) { + vTaskDelay(15000 / portTICK_PERIOD_MS); + MODLOG_DFLT(INFO, "Adding Dynamic service"); + /* add services defined in gatt_svr_svcs */ + dynamic_service(BLE_DYNAMIC_SERVICE_ADD, gatt_svr_svcs, NULL); + /* 15 seconds delay before deleting the service */ + vTaskDelay(15000 / portTICK_PERIOD_MS); + MODLOG_DFLT(INFO, "Deleting service"); + /* Delete the first service in the list */ + dynamic_service(BLE_DYNAMIC_SERVICE_DELETE, NULL, gatt_svr_svcs[0].uuid); + } + +} diff --git a/examples/bluetooth/nimble/ble_dynamic_service/sdkconfig.defaults b/examples/bluetooth/nimble/ble_dynamic_service/sdkconfig.defaults new file mode 100644 index 0000000000..5f32f430a2 --- /dev/null +++ b/examples/bluetooth/nimble/ble_dynamic_service/sdkconfig.defaults @@ -0,0 +1,13 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_DYNAMIC_SERVICE=y diff --git a/examples/bluetooth/nimble/ble_dynamic_service/tutorial/Ble_Dynamic_Service_Example_Walkthrough.md b/examples/bluetooth/nimble/ble_dynamic_service/tutorial/Ble_Dynamic_Service_Example_Walkthrough.md new file mode 100644 index 0000000000..a8c287b056 --- /dev/null +++ b/examples/bluetooth/nimble/ble_dynamic_service/tutorial/Ble_Dynamic_Service_Example_Walkthrough.md @@ -0,0 +1,193 @@ +# BLE Dynamic Service Example Walkthrough + +## Introduction + +In this tutorial, the ble_dynamic_service example code for the espressif chipsets is reviewed. This example creates GATT server and then starts advertising, waiting to be connected to a GATT client. +In the main thread it keeps on adding and deleting one custom service in the gatt server. When any of the connected clients subscribes for the Service Changed Characteristic(0x2a05) in gatt service, then whenever the gatt database is altered due to additition or deletion of the service the service changed indication is sent to the subscribed clients. + + +## Includes + +This example is located in the examples folder of the ESP-IDF under the [ble_dynamic_service/main](../main/). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are: + +```c +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#include "ble_dynamic_service.h" +``` + +These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_dynamic_service.h”` which expose the BLE APIs required to implement this example. + +* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack. +* `nimble_port_freertos.h`: Initializes and enables nimble host task. +* `ble_hs.h`: Defines the functionalities to handle the host event +* `ble_svc_gap.h`:Defines the macros for device name and device appearance and declares the function to set them. +* `“ble_dynamic_service.h`: Defines the example specific macros and functions. + +## Main Entry Point + +The program’s entry point is the app_main() function: + +```c +void +app_main(void) +{ + int rc; + + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(tag, "Failed to init nimble %d ", ret); + return; + } + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = dynamic_service_on_reset; + ble_hs_cfg.sync_cb = dynamic_service_on_sync; + ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + rc = gatt_svr_init(); + assert(rc == 0); + + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("ble-dynamic-service"); + assert(rc == 0); + + nimble_port_freertos_init(dynamic_service_host_task); + + while(1) { + vTaskDelay(15000 / portTICK_PERIOD_MS); + MODLOG_DFLT(INFO, "Adding Dynamic service"); + /* add services defined in gatt_svr_svcs */ + dynamic_service(BLE_DYNAMIC_SERVICE_ADD, gatt_svr_svcs, NULL); + /* 15 seconds delay before deleting the service */ + vTaskDelay(15000 / portTICK_PERIOD_MS); + MODLOG_DFLT(INFO, "Deleting service"); + /* Delete the first service in the list */ + dynamic_service(BLE_DYNAMIC_SERVICE_DELETE, NULL, gatt_svr_svcs[0].uuid); + } +} +``` + +The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations. + +```c +esp_err_t ret = nvs_flash_init(); +if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); +} +ESP_ERROR_CHECK( ret ); +``` + +## BT Controller and Stack Initialization +The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions: + +```c +esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +ret = esp_bt_controller_init(&config_opts); +``` + +Next, the controller is enabled in BLE Mode. + +```c +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); +``` +>The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode. + +There are four Bluetooth modes supported: + +1. `ESP_BT_MODE_IDLE`: Bluetooth not running +2. `ESP_BT_MODE_BLE`: BLE mode +3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode +4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic) + +After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`: + +```c +esp_err_t esp_nimble_init(void) +{ + +#if !SOC_ESP_NIMBLE_CONTROLLER + /* Initialize the function pointers for OS porting */ + npl_freertos_funcs_init(); + + npl_freertos_mempool_init(); + + if(esp_nimble_hci_init() != ESP_OK) { + ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n"); + return ESP_FAIL; + } + + /* Initialize default event queue */ + ble_npl_eventq_init(&g_eventq_dflt); + /* Initialize the global memory pool */ + os_mempool_module_init(); + os_msys_init(); + +#endif + /* Initialize the host */ + ble_transport_hs_init(); + + return ESP_OK; +} +``` + +The host is configured by setting up the callbacks on Stack-reset, Stack-sync, registration of each GATT resource, and storage status. + +```c + ble_hs_cfg.reset_cb = dynamic_service_on_reset; + ble_hs_cfg.sync_cb = dynamic_service_on_sync; + ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; +``` + +The main function then calls `ble_svc_gap_device_name_set()` to set the default device name. 'ble-dynamic-service' is passed as the default device name to this function. +```c +static const char *device_name = "ble-dynamic-service" +rc = ble_svc_gap_device_name_set(device_name); +``` + +The main function then creates a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`. +```c +nimble_port_freertos_init(dynamic_service_host_task); + +``` +`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but since something needs to handle the default queue, it is easier to create a separate task. + + +In the while loop, main thread keeps adding a custom service defined in struct `gatt_svr_svcs` and deleting it after 15 seconds. + +```c +while(1) { + vTaskDelay(15000 / portTICK_PERIOD_MS); + MODLOG_DFLT(INFO, "Adding Dynamic service"); + /* add services defined in gatt_svr_svcs */ + dynamic_service(BLE_DYNAMIC_SERVICE_ADD, gatt_svr_svcs, NULL); + /* 15 seconds delay before deleting the service */ + vTaskDelay(15000 / portTICK_PERIOD_MS); + MODLOG_DFLT(INFO, "Deleting service"); + /* Delete the first service in the list */ + dynamic_service(BLE_DYNAMIC_SERVICE_DELETE, NULL, gatt_svr_svcs[0].uuid); +} +``` +>Service change indication will be sent to all the subscribed clients after addition or deletion of service as the gatt database is altered. + +## Conclusion + +This Walkthrough covers the code explanation of the BLE_DYNAMIC_SERVICE example. The following points are concluded through this walkthrough. + 1. Gatt server keeps advertising and connections are allowed. + 2. The addition and deletion of custom service happens asynchronously and the service change indication is sent to the subscribed clients. From 0fd1300d50dbcea8fbeb769916f2b2de2aefc954 Mon Sep 17 00:00:00 2001 From: Roshan Bangar Date: Mon, 31 Jul 2023 15:32:07 +0530 Subject: [PATCH 06/21] feat(nimble): added HID over Gatt profile support --- components/bt/CMakeLists.txt | 4 + components/bt/host/nimble/Kconfig.in | 23 + .../host/nimble/port/include/esp_nimble_cfg.h | 23 + components/esp_hid/CMakeLists.txt | 5 + components/esp_hid/include/esp_hidh_nimble.h | 28 + .../esp_hid/include/esp_hidh_transport.h | 22 +- components/esp_hid/private/ble_hidd.h | 20 +- components/esp_hid/private/ble_hidh.h | 24 +- components/esp_hid/private/esp_hidh_private.h | 25 +- components/esp_hid/src/esp_hid_common.c | 18 +- components/esp_hid/src/esp_hidd.c | 4 +- components/esp_hid/src/esp_hidh.c | 109 +- components/esp_hid/src/nimble_hidd.c | 710 ++++++++++++ components/esp_hid/src/nimble_hidh.c | 1020 +++++++++++++++++ .../esp_hid_device/main/Kconfig.projbuild | 38 + .../esp_hid_device/main/esp_hid_device_main.c | 330 +++++- .../esp_hid_device/main/esp_hid_gap.c | 262 ++++- .../esp_hid_device/main/esp_hid_gap.h | 16 +- .../bluetooth/esp_hid_host/main/esp_hid_gap.c | 320 +++++- .../bluetooth/esp_hid_host/main/esp_hid_gap.h | 25 +- .../esp_hid_host/main/esp_hid_host_main.c | 51 + tools/ci/check_copyright_ignore.txt | 4 - 22 files changed, 3003 insertions(+), 78 deletions(-) create mode 100644 components/esp_hid/include/esp_hidh_nimble.h create mode 100644 components/esp_hid/src/nimble_hidd.c create mode 100644 components/esp_hid/src/nimble_hidh.c create mode 100644 examples/bluetooth/esp_hid_device/main/Kconfig.projbuild diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index a09f15ba0c..ad969579a6 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -549,6 +549,8 @@ if(CONFIG_BT_ENABLED) host/nimble/nimble/nimble/host/services/ipss/include host/nimble/nimble/nimble/host/services/lls/include host/nimble/nimble/nimble/host/services/tps/include + host/nimble/nimble/nimble/host/services/hid/include + host/nimble/nimble/nimble/host/services/sps/include host/nimble/nimble/nimble/host/util/include host/nimble/nimble/nimble/host/store/ram/include host/nimble/nimble/nimble/host/store/config/include @@ -565,6 +567,8 @@ if(CONFIG_BT_ENABLED) "host/nimble/nimble/nimble/host/services/bas/src/ble_svc_bas.c" "host/nimble/nimble/nimble/host/services/dis/src/ble_svc_dis.c" "host/nimble/nimble/nimble/host/services/lls/src/ble_svc_lls.c" + "host/nimble/nimble/nimble/host/services/hid/src/ble_svc_hid.c" + "host/nimble/nimble/nimble/host/services/sps/src/ble_svc_sps.c" "host/nimble/nimble/nimble/host/src/ble_hs_conn.c" "host/nimble/nimble/nimble/host/src/ble_store_util.c" "host/nimble/nimble/nimble/host/src/ble_sm.c" diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 11e916ecec..2a4365ac74 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -884,3 +884,26 @@ menu "GAP Service" Timeout = Value * 10 ms endmenu + +menu "BLE Services" + menuconfig BT_NIMBLE_HID_SERVICE + bool "HID service" + depends on BT_NIMBLE_ENABLED + default n + help + Enable HID service support + + config BT_NIMBLE_SVC_HID_MAX_INSTANCES + depends on BT_NIMBLE_HID_SERVICE + int "Maximum HID service instances" + default 2 + help + Defines maximum number of HID service instances + + config BT_NIMBLE_SVC_HID_MAX_RPTS + depends on BT_NIMBLE_HID_SERVICE + int "Maximum HID Report characteristics per service instance" + default 3 + help + Defines maximum number of report characteristics per service instance +endmenu diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 0a6d4812a5..6098e142c4 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -883,6 +883,20 @@ #ifndef MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_READ_PERM #define MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_READ_PERM (0) #endif + +/*** nimble/host/services/hid */ +#ifndef MYNEWT_VAL_BLE_SVC_HID_SERVICE +#define MYNEWT_VAL_BLE_SVC_HID_SERVICE CONFIG_BT_NIMBLE_HID_SERVICE +#endif + +#ifndef MYNEWT_VAL_BLE_SVC_HID_MAX_RPTS +#define MYNEWT_VAL_BLE_SVC_HID_MAX_RPTS CONFIG_BT_NIMBLE_SVC_HID_MAX_RPTS +#endif + +#ifndef MYNEWT_VAL_BLE_SVC_HID_MAX_SVC_INSTANCES +#define MYNEWT_VAL_BLE_SVC_HID_MAX_SVC_INSTANCES CONFIG_BT_NIMBLE_SVC_HID_MAX_INSTANCES +#endif + #ifndef MYNEWT_VAL_BLE_MESH_ADV_TASK_PRIO #define MYNEWT_VAL_BLE_MESH_ADV_TASK_PRIO (9) #endif @@ -1506,6 +1520,15 @@ #define MYNEWT_VAL_BLE_SVC_DIS_SYSTEM_ID_READ_PERM (-1) #endif +#ifndef MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_DEFAULT +#define MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_DEFAULT (NULL) +#endif + +/* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ +#ifndef MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_READ_PERM +#define MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_READ_PERM (-1) +#endif + /*** @apache-mynewt-nimble/nimble/host/services/gap */ #ifndef MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE #define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE diff --git a/components/esp_hid/CMakeLists.txt b/components/esp_hid/CMakeLists.txt index 36841a9957..39b7b64733 100644 --- a/components/esp_hid/CMakeLists.txt +++ b/components/esp_hid/CMakeLists.txt @@ -13,6 +13,11 @@ if(CONFIG_BT_ENABLED) "src/bt_hidh.c" "src/bt_hidd.c") endif() + if(CONFIG_BT_NIMBLE_ENABLED) + list(APPEND srcs + "src/nimble_hidd.c" + "src/nimble_hidh.c") + endif() endif() idf_component_register(SRCS "${srcs}" diff --git a/components/esp_hid/include/esp_hidh_nimble.h b/components/esp_hid/include/esp_hidh_nimble.h new file mode 100644 index 0000000000..b96a02d2bd --- /dev/null +++ b/components/esp_hid/include/esp_hidh_nimble.h @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif +#include "sdkconfig.h" + +#if CONFIG_BT_NIMBLE_ENABLED +/** + * @brief Open BlueTooth HID Device using BlueDroid + * @param bda : BT Device Address + * @param transport : BT Device Protocol (Classic/HID) + * @param remote_addr_type : BLE Remote address type + * + * @return: ESP_OK on success + */ +esp_hidh_dev_t *esp_hidh_dev_open(uint8_t *bda, esp_hid_transport_t transport, uint8_t remote_addr_type); +#endif /* CONFIG_BT_NIMBLE_ENABLED */ + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_hid/include/esp_hidh_transport.h b/components/esp_hid/include/esp_hidh_transport.h index 22138aef8e..f8420b852a 100644 --- a/components/esp_hid/include/esp_hidh_transport.h +++ b/components/esp_hid/include/esp_hidh_transport.h @@ -1,16 +1,8 @@ -// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -28,6 +20,10 @@ extern "C" { #include "esp_hidh_bluedroid.h" #endif +#if CONFIG_BT_NIMBLE_ENABLED +#include "esp_hidh_nimble.h" +#endif + #ifdef __cplusplus } #endif diff --git a/components/esp_hid/private/ble_hidd.h b/components/esp_hid/private/ble_hidd.h index 087d816c03..5556b583e6 100644 --- a/components/esp_hid/private/ble_hidd.h +++ b/components/esp_hid/private/ble_hidd.h @@ -1,16 +1,8 @@ -// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -22,7 +14,7 @@ extern "C" { #endif -#if CONFIG_GATTS_ENABLE +#if CONFIG_GATTS_ENABLE || CONFIG_BT_NIMBLE_ENABLED esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev, const esp_hid_device_config_t *config, esp_event_handler_t callback); diff --git a/components/esp_hid/private/ble_hidh.h b/components/esp_hid/private/ble_hidh.h index 131f064805..538c13e14c 100644 --- a/components/esp_hid/private/ble_hidh.h +++ b/components/esp_hid/private/ble_hidh.h @@ -1,16 +1,8 @@ -// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -20,12 +12,16 @@ extern "C" { #endif -#if CONFIG_GATTC_ENABLE +#if CONFIG_GATTC_ENABLE || CONFIG_BT_NIMBLE_ENABLED esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config); esp_err_t esp_ble_hidh_deinit(void); +#if CONFIG_BT_NIMBLE_ENABLED +esp_hidh_dev_t *esp_ble_hidh_dev_open(uint8_t *bda, uint8_t address_type); +#else esp_hidh_dev_t *esp_ble_hidh_dev_open(esp_bd_addr_t bda, esp_ble_addr_type_t address_type); +#endif #endif /* CONFIG_GATTC_ENABLE */ diff --git a/components/esp_hid/private/esp_hidh_private.h b/components/esp_hid/private/esp_hidh_private.h index a6ffda9443..c7494998f2 100644 --- a/components/esp_hid/private/esp_hidh_private.h +++ b/components/esp_hid/private/esp_hidh_private.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,6 +17,9 @@ #include "esp_event.h" #include "sys/queue.h" #include "esp_timer.h" +#if CONFIG_BT_NIMBLE_ENABLED +#include "nimble/ble.h" +#endif #ifdef __cplusplus extern "C" { @@ -52,7 +55,11 @@ struct esp_hidh_dev_s { esp_timer_handle_t trans_timer; //transactiion timer uint8_t report_type; //Get_Report tansaction report_type uint8_t report_id; //Get_Report tansaction report_id +#if CONFIG_BT_NIMBLE_ENABLED + uint8_t *protocol_mode; // protocol mode is unique for each hid service instance +#else uint8_t protocol_mode; //device protocol mode +#endif bool connected; //we have all required data to communicate bool opened; //we opened the device manually, else the device connected to us bool added; //If lower layer has added the device @@ -82,6 +89,9 @@ struct esp_hidh_dev_s { #if CONFIG_BLUEDROID_ENABLED esp_bd_addr_t bda; #endif /* CONFIG_BLUEDROID_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED + uint8_t bda[6]; +#endif union { #if CONFIG_BT_HID_HOST_ENABLED @@ -102,6 +112,15 @@ struct esp_hidh_dev_s { uint16_t battery_ccc_handle; } ble; #endif /* CONFIG_GATTC_ENABLE */ +#if CONFIG_BT_NIMBLE_ENABLED + struct { + uint8_t address_type; + int conn_id; + uint16_t appearance; + uint16_t battery_handle; + uint16_t battery_ccc_handle; + } ble; +#endif }; TAILQ_ENTRY(esp_hidh_dev_s) devices; }; @@ -115,6 +134,10 @@ esp_hidh_dev_t *esp_hidh_dev_get_by_bda(esp_bd_addr_t bda); //BT/BLE esp_hidh_dev_t *esp_hidh_dev_get_by_handle(uint8_t handle); //Classic Bluetooth Only esp_hidh_dev_t *esp_hidh_dev_get_by_conn_id(uint16_t conn_id); //BLE Only #endif /* CONFIG_BLUEDROID_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED +esp_hidh_dev_t *esp_hidh_dev_get_by_bda(uint8_t* bda); // BLE Only +esp_hidh_dev_t *esp_hidh_dev_get_by_conn_id(uint16_t conn_id); //BLE Only +#endif esp_hidh_dev_report_t *esp_hidh_dev_get_report_by_id_type_proto(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, uint8_t protocol_mode); esp_hidh_dev_report_t *esp_hidh_dev_get_report_by_id_and_type(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type); diff --git a/components/esp_hid/src/esp_hid_common.c b/components/esp_hid/src/esp_hid_common.c index b549d00663..ee4e813ef4 100644 --- a/components/esp_hid/src/esp_hid_common.c +++ b/components/esp_hid/src/esp_hid_common.c @@ -1,16 +1,8 @@ -// Copyright 2017-2019 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include "esp_log.h" diff --git a/components/esp_hid/src/esp_hidd.c b/components/esp_hid/src/esp_hidd.c index b3216023cc..571f963d65 100644 --- a/components/esp_hid/src/esp_hidd.c +++ b/components/esp_hid/src/esp_hidd.c @@ -8,7 +8,7 @@ #include "esp_hidd_private.h" #include "esp_event_base.h" -#if CONFIG_GATTS_ENABLE +#if CONFIG_GATTS_ENABLE || CONFIG_BT_NIMBLE_ENABLED #include "ble_hidd.h" #endif /* CONFIG_GATTS_ENABLE */ @@ -27,7 +27,7 @@ esp_err_t esp_hidd_dev_init(const esp_hid_device_config_t *config, esp_hid_trans } switch (transport) { -#if CONFIG_GATTS_ENABLE +#if CONFIG_GATTS_ENABLE || CONFIG_BT_NIMBLE_ENABLED case ESP_HID_TRANSPORT_BLE: ret = esp_ble_hidd_dev_init(dev, config, callback); break; diff --git a/components/esp_hid/src/esp_hidh.c b/components/esp_hid/src/esp_hidh.c index be5a61e479..ac37f71709 100644 --- a/components/esp_hid/src/esp_hidh.c +++ b/components/esp_hid/src/esp_hidh.c @@ -88,7 +88,7 @@ esp_err_t esp_hidh_init(const esp_hidh_config_t *config) } #endif /* CONFIG_BT_HID_HOST_ENABLED */ -#if CONFIG_GATTC_ENABLE +#if CONFIG_GATTC_ENABLE || CONFIG_BT_NIMBLE_ENABLED if (err == ESP_OK) { err = esp_ble_hidh_init(config); } @@ -123,7 +123,7 @@ esp_err_t esp_hidh_deinit(void) } #endif /* CONFIG_BT_HID_HOST_ENABLED */ -#if CONFIG_GATTC_ENABLE +#if CONFIG_GATTC_ENABLE || CONFIG_BT_NIMBLE_ENABLED if (err == ESP_OK) { err = esp_ble_hidh_deinit(); } @@ -150,6 +150,11 @@ esp_hidh_dev_t *esp_hidh_dev_open(esp_bd_addr_t bda, esp_hid_transport_t transpo dev = esp_ble_hidh_dev_open(bda, (esp_ble_addr_type_t)remote_addr_type); } #endif /* CONFIG_GATTC_ENABLE */ +#if CONFIG_BT_NIMBLE_ENABLED + if (transport == ESP_HID_TRANSPORT_BLE) { + dev = esp_ble_hidh_dev_open(bda, remote_addr_type); + } +#endif /* CONFIG_BT_NIMBLE_ENABLED */ #if CONFIG_BT_HID_HOST_ENABLED if (transport == ESP_HID_TRANSPORT_BT) { dev = esp_bt_hidh_dev_open(bda); @@ -159,6 +164,19 @@ esp_hidh_dev_t *esp_hidh_dev_open(esp_bd_addr_t bda, esp_hid_transport_t transpo } #endif /* CONFIG_BLUEDROID_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED +esp_hidh_dev_t *esp_hidh_dev_open(uint8_t *bda, esp_hid_transport_t transport, uint8_t remote_addr_type) +{ + if (esp_hidh_dev_get_by_bda(bda) != NULL) { + ESP_LOGE(TAG, "Already Connected"); + return NULL; + } + esp_hidh_dev_t *dev = NULL; + dev = esp_ble_hidh_dev_open(bda, remote_addr_type); + return dev; +} +#endif /* CONFIG_BT_NIMBLE_ENABLED */ + esp_err_t esp_hidh_dev_close(esp_hidh_dev_t *dev) { esp_err_t ret = ESP_OK; @@ -329,6 +347,14 @@ const uint8_t *esp_hidh_dev_bda_get(esp_hidh_dev_t *dev) esp_hidh_dev_unlock(dev); } #endif /* CONFIG_BLUEDROID_ENABLED */ + +#if CONFIG_BT_NIMBLE_ENABLED + if (esp_hidh_dev_exists(dev)) { + esp_hidh_dev_lock(dev); + ret = dev->bda; + esp_hidh_dev_unlock(dev); + } +#endif /* CONFIG_BT_NIMBLE_ENABLED */ return ret; } @@ -828,3 +854,82 @@ void esp_hidh_post_process_event_handler(void *event_handler_arg, esp_event_base } } #endif /* CONFIG_BLUEDROID_ENABLED */ + +#if CONFIG_BT_NIMBLE_ENABLED +esp_hidh_dev_t *esp_hidh_dev_get_by_bda(uint8_t *bda) +{ + esp_hidh_dev_t * d = NULL; + lock_devices(); + TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) { + if (memcmp(bda, d->bda, sizeof(uint8_t) * 6) == 0) { + unlock_devices(); + return d; + } + } + unlock_devices(); + return NULL; +} + +esp_hidh_dev_t *esp_hidh_dev_get_by_conn_id(uint16_t conn_id) +{ + esp_hidh_dev_t * d = NULL; + lock_devices(); + TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) { + if (d->ble.conn_id == conn_id) { + unlock_devices(); + return d; + } + } + unlock_devices(); + return NULL; +} + +/** + * The deep copy data append the end of the esp_hidh_event_data_t, move the data pointer to the correct address. This is + * a workaround way, it's better to use flexible array in the interface. + */ +void esp_hidh_preprocess_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, + void *event_data) +{ + esp_hidh_event_t event = (esp_hidh_event_t)event_id; + esp_hidh_event_data_t *param = (esp_hidh_event_data_t *)event_data; + + switch (event) { + case ESP_HIDH_INPUT_EVENT: + if (param->input.length && param->input.data) { + param->input.data = (uint8_t *)param + sizeof(esp_hidh_event_data_t); + } + break; + case ESP_HIDH_FEATURE_EVENT: + if (param->feature.length && param->feature.data) { + param->feature.data = (uint8_t *)param + sizeof(esp_hidh_event_data_t); + } + break; + default: + break; + } +} + +void esp_hidh_post_process_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, + void *event_data) +{ + esp_hidh_event_t event = (esp_hidh_event_t)event_id; + esp_hidh_event_data_t *param = (esp_hidh_event_data_t *)event_data; + + switch (event) { + case ESP_HIDH_OPEN_EVENT: + if (param->open.status != ESP_OK) { + esp_hidh_dev_t *dev = param->open.dev; + if (dev) { + esp_hidh_dev_free_inner(dev); + } + } + break; + case ESP_HIDH_CLOSE_EVENT: + esp_hidh_dev_free_inner(param->close.dev); + break; + default: + break; + } +} +#endif /* CONFIG_BT_NIMBLE_ENABLED */ diff --git a/components/esp_hid/src/nimble_hidd.c b/components/esp_hid/src/nimble_hidd.c new file mode 100644 index 0000000000..0bfda9e9d2 --- /dev/null +++ b/components/esp_hid/src/nimble_hidd.c @@ -0,0 +1,710 @@ +/* + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "ble_hidd.h" +#include "esp_hidd_private.h" +#include "esp_log.h" + +#include +#include +#include +#include "nimble/nimble_opt.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "host/ble_hs_hci.h" +#include "host/ble_att.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#include "services/bas/ble_svc_bas.h" +#include "services/hid/ble_svc_hid.h" +#include "services/dis/ble_svc_dis.h" +#include "services/sps/ble_svc_sps.h" + +#if CONFIG_BT_NIMBLE_HID_SERVICE + +static const char *TAG = "NIMBLE_HIDD"; +#define BLE_SVC_BAS_UUID16 0x180F + + +typedef struct esp_ble_hidd_dev_s esp_ble_hidd_dev_t; +// there can be only one BLE HID device +static esp_ble_hidd_dev_t *s_dev = NULL; + +typedef hidd_report_item_t hidd_le_report_item_t; + +typedef struct { + esp_hid_raw_report_map_t reports_map; + uint8_t reports_len; + hidd_le_report_item_t *reports; + uint16_t hid_svc; + uint16_t hid_control_handle; + uint16_t hid_protocol_handle; +} hidd_dev_map_t; + + + +struct esp_ble_hidd_dev_s { + esp_hidd_dev_t *dev; + esp_event_loop_handle_t event_loop_handle; + esp_hid_device_config_t config; + uint16_t appearance; + + bool connected; + uint16_t conn_id; + + uint8_t control; // 0x00 suspend, 0x01 suspend off + uint8_t protocol; // 0x00 boot, 0x01 report + + uint16_t bat_svc_handle; + uint16_t info_svc_handle; + struct ble_gatt_svc hid_incl_svc; + + uint16_t bat_level_handle; + uint8_t pnp[7]; /* something related to device info service */ + hidd_dev_map_t *devices; + uint8_t devices_len; +}; + +// HID Information characteristic value +static const uint8_t hidInfo[4] = { + 0x11, 0x01, // bcdHID (USB HID version) + 0x00, // bCountryCode + ESP_HID_FLAGS_REMOTE_WAKE | ESP_HID_FLAGS_NORMALLY_CONNECTABLE // Flags +}; + +static int create_hid_db(int device_index) +{ + int rc = 0; + struct ble_svc_hid_params hparams = {0}; + int report_mode_rpts = 0; + + /* fill hid info */ + memcpy(&hparams.hid_info, hidInfo, sizeof hparams.hid_info); + + /* fill report map */ + memcpy(&hparams.report_map, (uint8_t *)s_dev->devices[device_index].reports_map.data, s_dev->devices[device_index].reports_map.len); + hparams.report_map_len = s_dev->devices[device_index].reports_map.len; + hparams.external_rpt_ref = BLE_SVC_BAS_UUID16; + + /* fill protocol mode */ + hparams.proto_mode_present = 1; + hparams.proto_mode = s_dev->protocol; + + for (uint8_t i = 0; i < s_dev->devices[device_index].reports_len; i++) { + hidd_le_report_item_t *report = &s_dev->devices[device_index].reports[i]; + if (report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) { + /* only consider report mode reports, all boot mode reports will be registered by default */ + if (report->report_type == ESP_HID_REPORT_TYPE_INPUT) { + /* Input Report */ + hparams.rpts[report_mode_rpts].type = ESP_HID_REPORT_TYPE_INPUT; + } else if (report->report_type == ESP_HID_REPORT_TYPE_OUTPUT) { + /* Output Report */ + hparams.rpts[report_mode_rpts].type = ESP_HID_REPORT_TYPE_OUTPUT; + } else { + /* Feature Report */ + hparams.rpts[report_mode_rpts].type = ESP_HID_REPORT_TYPE_FEATURE; + } + hparams.rpts[report_mode_rpts].id = report->report_id; + report_mode_rpts++; + } else { + if (report->report_type == ESP_HID_REPORT_TYPE_INPUT) { + /* Boot mode reports */ + if (report->usage == ESP_HID_USAGE_KEYBOARD) { //Boot Keyboard Input + hparams.kbd_inp_present = 1; + } else { //Boot Mouse Input + hparams.mouse_inp_present = 1; + } + } else { //Boot Keyboard Output + hparams.kbd_out_present = 1; + } + } + } + hparams.rpts_len = report_mode_rpts; + /* Add service */ + rc = ble_svc_hid_add(hparams); + if(rc != 0) { + return rc; + } + return rc; +} + + +static int ble_hid_create_info_db() { + int rc; + + rc = 0; + ble_svc_dis_init(); + uint8_t pnp_val[7] = { + 0x02, //0x1=BT, 0x2=USB + s_dev->config.vendor_id & 0xFF, (s_dev->config.vendor_id >> 8) & 0xFF, //VID + s_dev->config.product_id & 0xFF, (s_dev->config.product_id >> 8) & 0xFF, //PID + s_dev->config.version & 0xFF, (s_dev->config.version >> 8) & 0xFF //VERSION + }; + memcpy(s_dev->pnp, pnp_val, 7); + ble_svc_dis_pnp_id_set((char *)s_dev->pnp); + if (s_dev->config.manufacturer_name && s_dev->config.manufacturer_name[0]) { + rc = ble_svc_dis_manufacturer_name_set(s_dev->config.manufacturer_name); + } + if (s_dev->config.serial_number && s_dev->config.serial_number[0]) { + rc = ble_svc_dis_serial_number_set(s_dev->config.serial_number); + } + return rc; +} + +static int nimble_hid_start_gatts(void) +{ + int rc = ESP_OK; + + ble_svc_gap_init(); + ble_svc_gatt_init(); + ble_svc_sps_init(0, 0); // initialize with 0 + ble_svc_bas_init(); + ble_hid_create_info_db(); + + for (uint8_t d = 0; d < s_dev->devices_len; d++) { + rc = create_hid_db(d); + if(rc != 0) { + return rc; + } + } + /* init the hid svc */ + ble_svc_hid_init(); + + return rc; +} + +static int nimble_hid_stop_gatts(esp_ble_hidd_dev_t *dev) +{ + int rc = ESP_OK; + + /* stop gatt database */ + ble_gatts_stop(); + return rc; +} + +/* Identify the reports using the report map */ +static int ble_hid_init_config(esp_ble_hidd_dev_t *dev, const esp_hid_device_config_t *config) +{ + memset((uint8_t *)(&dev->config), 0, sizeof(esp_hid_device_config_t)); + dev->config.vendor_id = config->vendor_id; + dev->config.product_id = config->product_id; + dev->config.version = config->version; + if (config->device_name != NULL) { + dev->config.device_name = strdup(config->device_name); + } + if (config->manufacturer_name != NULL) { + dev->config.manufacturer_name = strdup(config->manufacturer_name); + } + if (config->serial_number != NULL) { + dev->config.serial_number = strdup(config->serial_number); + } + dev->appearance = ESP_HID_APPEARANCE_GENERIC; + + if (config->report_maps_len) { + dev->devices = (hidd_dev_map_t *)malloc(config->report_maps_len * sizeof(hidd_dev_map_t)); + if (dev->devices == NULL) { + ESP_LOGE(TAG, "devices malloc(%d) failed", config->report_maps_len); + return ESP_FAIL; + } + memset(dev->devices, 0, config->report_maps_len * sizeof(hidd_dev_map_t)); + dev->devices_len = config->report_maps_len; + for (uint8_t d = 0; d < dev->devices_len; d++) { + + //raw report map + uint8_t *map = (uint8_t *)malloc(config->report_maps[d].len); + if (map == NULL) { + ESP_LOGE(TAG, "report map malloc(%d) failed", config->report_maps[d].len); + return ESP_FAIL; + } + memcpy(map, config->report_maps[d].data, config->report_maps[d].len); + + dev->devices[d].reports_map.data = (const uint8_t *)map; + dev->devices[d].reports_map.len = config->report_maps[d].len; + + esp_hid_report_map_t *rmap = esp_hid_parse_report_map(config->report_maps[d].data, config->report_maps[d].len); + if (rmap == NULL) { + ESP_LOGE(TAG, "hid_parse_report_map[%d](%d) failed", d, config->report_maps[d].len); + return ESP_FAIL; + } + dev->appearance = rmap->appearance; + dev->devices[d].reports_len = rmap->reports_len; + dev->devices[d].reports = (hidd_le_report_item_t *)malloc(rmap->reports_len * sizeof(hidd_le_report_item_t)); + if (dev->devices[d].reports == NULL) { + ESP_LOGE(TAG, "reports malloc(%d) failed", rmap->reports_len * sizeof(hidd_le_report_item_t)); + free(rmap); + return ESP_FAIL; + } + for (uint8_t r = 0; r < rmap->reports_len; r++) { + dev->devices[d].reports[r].map_index = d; + dev->devices[d].reports[r].report_id = rmap->reports[r].report_id; + dev->devices[d].reports[r].protocol_mode = rmap->reports[r].protocol_mode; + dev->devices[d].reports[r].report_type = rmap->reports[r].report_type; + dev->devices[d].reports[r].usage = rmap->reports[r].usage; + dev->devices[d].reports[r].value_len = rmap->reports[r].value_len; + } + free(rmap->reports); + free(rmap); + } + } + return ESP_OK; +} + +static int ble_hid_free_config(esp_ble_hidd_dev_t *dev) +{ + for (uint8_t d = 0; d < dev->devices_len; d++) { + free((void *)dev->devices[d].reports); + free((void *)dev->devices[d].reports_map.data); + } + + free((void *)dev->devices); + free((void *)dev->config.device_name); + free((void *)dev->config.manufacturer_name); + free((void *)dev->config.serial_number); + if (dev->event_loop_handle != NULL) { + esp_event_loop_delete(dev->event_loop_handle); + } + return ESP_OK; +} + +static int nimble_hidd_dev_deinit(void *devp) { + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + if (!s_dev) { + ESP_LOGE(TAG, "HID device profile already uninitialized"); + return ESP_OK; + } + + if (s_dev != dev) { + ESP_LOGE(TAG, "Wrong HID device provided"); + return ESP_FAIL; + } + s_dev = NULL; + + nimble_hid_stop_gatts(dev); + esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_STOP_EVENT, NULL, 0, portMAX_DELAY); + ble_hid_free_config(dev); + free(dev); + return ESP_OK; +} + +static bool nimble_hidd_dev_connected(void *devp) +{ + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + return (dev != NULL && s_dev == dev && dev->connected); +} + +static int nimble_hidd_dev_battery_set(void *devp, uint8_t level) +{ + int rc; + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + + if (!dev->connected) { + /* Return success if not yet connected */ + return ESP_OK; + } + + rc = ble_svc_bas_battery_level_set(level); + if (rc) { + ESP_LOGE(TAG, "esp_ble_gatts_send_notify failed: %d", rc); + return ESP_FAIL; + } + + return ESP_OK; +} + +/* if mode is NULL, find the first matching report */ +static hidd_le_report_item_t* find_report(uint8_t id, uint8_t type, uint8_t *mode) { + hidd_le_report_item_t *rpt; + for (uint8_t d = 0; d < s_dev->devices_len; d++) { + for (uint8_t i = 0; i < s_dev->devices[d].reports_len; i++) { + rpt = &s_dev->devices[d].reports[i]; + if(rpt->report_id == id && rpt->report_type == type && (!mode || (mode && *mode == rpt->protocol_mode))) { + return rpt; + } + } + } + return NULL; +} +static hidd_le_report_item_t* find_report_by_usage_and_type(uint8_t usage, uint8_t type, uint8_t *mode) { + hidd_le_report_item_t *rpt; + for (uint8_t d = 0; d < s_dev->devices_len; d++) { + for (uint8_t i = 0; i < s_dev->devices[d].reports_len; i++) { + rpt = &s_dev->devices[d].reports[i]; + if(rpt->usage == usage && rpt->report_type == type && (!mode || (mode && *mode == rpt->protocol_mode))) { + return rpt; + } + } + } + return NULL; +} + +static int nimble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length) +{ + hidd_le_report_item_t *p_rpt; + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + int rc; + struct os_mbuf *om; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + + if (!dev->connected) { + ESP_LOGE(TAG, "%s Device Not Connected", __func__); + return ESP_FAIL; + } + + /* check the protocol mode */ + /* as the protocol mode is always present, its safe to read the characteristic */ + rc = ble_att_svr_read_local(s_dev->devices[index].hid_protocol_handle, &om); + if(rc != 0) { + ESP_LOGE(TAG, "Unable to fetch protocol_mode\n"); + return ESP_FAIL; + } + rc = ble_hs_mbuf_to_flat(om, &dev->protocol, sizeof(dev->protocol), NULL); + if(rc != 0) { + return ESP_FAIL; + } + /* free the mbuf */ + os_mbuf_free_chain(om); + om = NULL; + + p_rpt = find_report(id, ESP_HID_REPORT_TYPE_INPUT, &dev->protocol); + assert(p_rpt != NULL); + om = ble_hs_mbuf_from_flat((void*)data, length); + assert(om != NULL); + /* NOTE : om is freed by stack */ + rc = ble_att_svr_write_local(p_rpt->handle, om); + if (rc != 0) { + ESP_LOGE(TAG, "Write Input Report Failed: %d", rc); + return ESP_FAIL; + } + return ESP_OK; +} + +static int nimble_hidd_dev_feature_set(void *devp, size_t index, size_t id, uint8_t *data, size_t length) +{ + /* This function is a no-op for now */ + hidd_le_report_item_t *p_rpt; + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + int rc; + struct os_mbuf *om; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + + if (!dev->connected) { + ESP_LOGE(TAG, "%s Device Not Connected", __func__); + return ESP_FAIL; + } + + /* check the protocol mode */ + /* as the protocol mode is always present, its safe to read the characteristic */ + rc = ble_att_svr_read_local(s_dev->devices[index].hid_protocol_handle, &om); + if(rc != 0) { + ESP_LOGE(TAG, "Unable to fetch protocol_mode\n"); + return ESP_FAIL; + } + rc = ble_hs_mbuf_to_flat(om, &dev->protocol, sizeof(dev->protocol), NULL); + if(rc != 0) { + return ESP_FAIL; + } + /* free the mbuf */ + os_mbuf_free_chain(om); + om = NULL; + + p_rpt = find_report(id, ESP_HID_REPORT_TYPE_FEATURE, &dev->protocol); + assert(p_rpt != NULL); + om = ble_hs_mbuf_from_flat((void*)data, length); + assert(om != NULL); + /* NOTE : om is freed by stack*/ + rc = ble_att_svr_write_local(p_rpt->handle, om); + if (rc != 0) { + ESP_LOGE(TAG, "Set Feature Report Failed: %d", rc); + return ESP_FAIL; + } + return ESP_OK; +} + +static int nimble_hidd_dev_event_handler_register(void *devp, esp_event_handler_t callback, esp_hidd_event_t event) +{ + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + return esp_event_handler_register_with(dev->event_loop_handle, ESP_HIDD_EVENTS, event, callback, dev->dev); +} + +static int esp_ble_hidd_dev_event_handler_unregister(void *devp, esp_event_handler_t callback, esp_hidd_event_t event) +{ + esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; + if (!dev || s_dev != dev) { + return ESP_FAIL; + } + return esp_event_handler_unregister_with(dev->event_loop_handle, ESP_HIDD_EVENTS, event, callback); +} + +static void ble_hidd_dev_free(void) +{ + if (s_dev) { + ble_hid_free_config(s_dev); + free(s_dev); + s_dev = NULL; + } +} + +static int nimble_hid_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + struct os_mbuf *om; + uint8_t data; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + ESP_LOGD(TAG, "connection %s; status=%d", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + + /* save connection handle */ + s_dev->connected = true; + s_dev->conn_id = event->connect.conn_handle; + esp_hidd_event_data_t cb_param = { + .connect.dev = s_dev->dev, + .connect.status = event->connect.status + }; + + /* reset the protocol mode value */ + data = ESP_HID_PROTOCOL_MODE_REPORT; + om = ble_hs_mbuf_from_flat(&data, 1); + if(om == NULL) { + ESP_LOGD(TAG, "No memory to allocate mbuf"); + } + /* NOTE : om is freed by stack */ + for(int i = 0; i < s_dev->devices_len; i++) { + rc = ble_att_svr_write_local(s_dev->devices[i].hid_protocol_handle, om); + if (rc != 0) { + ESP_LOGE(TAG, "Write on Protocol Mode Failed: %d", rc); + } + } + + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONNECT_EVENT, + &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); + return 0; + break; + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGD(TAG, "disconnect; reason=%d", event->disconnect.reason); + + if (s_dev->connected) { + s_dev->connected = false; + esp_hidd_event_data_t cb_param = {0}; + cb_param.disconnect.dev = s_dev->dev; + cb_param.disconnect.reason = event->disconnect.reason; + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_DISCONNECT_EVENT, + &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); + } + return 0; + } + return 0; +} + +/** service index is used to identify the hid service instance + of the registered characteristic. + Assuming the first instance of the hid service is registered first. + Increment service index as the hid services get registered */ +static int service_index = -1; +static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) +{ + char buf[BLE_UUID_STR_LEN]; + hidd_le_report_item_t *rpt = NULL; + struct os_mbuf *om; + uint16_t uuid16; + uint16_t report_info; + uint8_t report_type, report_id; + uint16_t report_handle; + uint8_t protocol_mode; + int rc; + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + ESP_LOGD(TAG, "registered service %s with handle=%d", + ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), + ctxt->svc.handle); + uuid16 = ble_uuid_u16(ctxt->svc.svc_def->uuid); + if(uuid16 == BLE_SVC_HID_UUID16) { + ++service_index; + s_dev->devices[service_index].hid_svc = ctxt->svc.handle; + } + + break; + + case BLE_GATT_REGISTER_OP_CHR: + ESP_LOGD(TAG, "registering characteristic %s with " + "def_handle=%d val_handle=%d\n", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), + ctxt->chr.def_handle, + ctxt->chr.val_handle); + uuid16 = ble_uuid_u16(ctxt->chr.chr_def->uuid); + if(uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT) { + /* assuming this characteristic is from the last registered hid service */ + s_dev->devices[service_index].hid_control_handle = ctxt->chr.val_handle; + } + if(uuid16 == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE) { + /* assuming this characteristic is from the last registered hid service */ + s_dev->devices[service_index].hid_protocol_handle = ctxt->chr.val_handle; + } + if(uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_INP) { + protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + rpt = find_report_by_usage_and_type(ESP_HID_USAGE_KEYBOARD, ESP_HID_REPORT_TYPE_INPUT, &protocol_mode); + if(rpt == NULL) { + ESP_LOGE(TAG, "Unknown boot kbd input report registration"); + return; + } + rpt->handle = ctxt->chr.val_handle; + } + if(uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_OUT) { + protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + rpt = find_report_by_usage_and_type(ESP_HID_USAGE_KEYBOARD, ESP_HID_REPORT_TYPE_OUTPUT, &protocol_mode); + if(rpt == NULL) { + ESP_LOGE(TAG, "Unknown boot kbd output report registration"); + return; + } + rpt->handle = ctxt->chr.val_handle; + } + if(uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_MOUSE_INP) { + protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + rpt = find_report_by_usage_and_type(ESP_HID_USAGE_MOUSE, ESP_HID_REPORT_TYPE_INPUT, &protocol_mode); + if(rpt == NULL) { + ESP_LOGE(TAG, "Unknown boot mouse input report registration"); + return; + } + rpt->handle = ctxt->chr.val_handle; + } + break; + + case BLE_GATT_REGISTER_OP_DSC: + ESP_LOGD(TAG, "registering descriptor %s with handle=%d", + ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), + ctxt->dsc.handle); + uuid16 = ble_uuid_u16(ctxt->dsc.dsc_def->uuid); + if(uuid16 == BLE_SVC_HID_DSC_UUID16_RPT_REF) { + rc = ble_att_svr_read_local(ctxt->dsc.handle, &om); + assert(rc == 0); + + ble_hs_mbuf_to_flat(om, &report_info, sizeof report_info, NULL); + report_type = (uint8_t)((report_info & 0xFF00) >> 8); + report_id = report_info & 0x00FF; + report_handle = (*(uint16_t*)(ctxt->dsc.dsc_def->arg)); + protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT; + rpt = find_report(report_id, report_type, &protocol_mode); + assert(rpt != NULL); + rpt->handle = report_handle; + /* free the mbuf */ + os_mbuf_free_chain(om); + om = NULL; + } + break; + + default: + assert(0); + break; + } +} + +static void nimble_host_synced(void) { + esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_START_EVENT, NULL, 0, portMAX_DELAY); +} + +void nimble_host_reset(int reason) +{ + MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); +} + +static struct ble_gap_event_listener nimble_gap_event_listener; +esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_config_t *config, esp_event_handler_t callback) +{ + int rc; + + if (s_dev) { + ESP_LOGE(TAG, "HID device profile already initialized"); + return ESP_FAIL; + } + + s_dev = (esp_ble_hidd_dev_t *)calloc(1, sizeof(esp_ble_hidd_dev_t)); + if (s_dev == NULL) { + ESP_LOGE(TAG, "HID device could not be allocated"); + return ESP_FAIL; + } + + // Reset the hid device target environment + s_dev->control = ESP_HID_CONTROL_EXIT_SUSPEND; + s_dev->protocol = ESP_HID_PROTOCOL_MODE_REPORT; + s_dev->event_loop_handle = NULL; + s_dev->dev = dev_p; + + esp_event_loop_args_t event_task_args = { + .queue_size = 5, + .task_name = "ble_hidd_events", + .task_priority = uxTaskPriorityGet(NULL), + .task_stack_size = 2048, + .task_core_id = tskNO_AFFINITY + }; + rc = esp_event_loop_create(&event_task_args, &s_dev->event_loop_handle); + if (rc != ESP_OK) { + ESP_LOGE(TAG, "HID device event loop could not be created"); + ble_hidd_dev_free(); + return rc; + } + + rc = ble_hid_init_config(s_dev, config); + if (rc != ESP_OK) { + ble_hidd_dev_free(); + return rc; + } + + dev_p->dev = s_dev; + dev_p->connected = nimble_hidd_dev_connected; + dev_p->deinit = nimble_hidd_dev_deinit; + dev_p->battery_set = nimble_hidd_dev_battery_set; + dev_p->input_set = nimble_hidd_dev_input_set; + dev_p->feature_set = nimble_hidd_dev_feature_set; + dev_p->event_handler_register = nimble_hidd_dev_event_handler_register; + dev_p->event_handler_unregister = esp_ble_hidd_dev_event_handler_unregister; + + rc = nimble_hidd_dev_event_handler_register(s_dev, esp_hidd_process_event_data_handler, ESP_EVENT_ANY_ID); + if (rc != ESP_OK) { + ble_hidd_dev_free(); + return rc; + } + + if (callback != NULL) { + rc = nimble_hidd_dev_event_handler_register(s_dev, callback, ESP_EVENT_ANY_ID); + if (rc != ESP_OK) { + ble_hidd_dev_free(); + return rc; + } + } + + ble_hs_cfg.reset_cb = nimble_host_reset; + ble_hs_cfg.sync_cb = nimble_host_synced; + ble_hs_cfg.gatts_register_cb = nimble_gatt_svr_register_cb; + rc = nimble_hid_start_gatts(); + if(rc != ESP_OK) { + return rc; + } + ble_gap_event_listener_register(&nimble_gap_event_listener, + nimble_hid_gap_event, NULL); + + return rc; +} +#endif // CONFIG_BT_NIMBLE_HID_SERVICE diff --git a/components/esp_hid/src/nimble_hidh.c b/components/esp_hid/src/nimble_hidh.c new file mode 100644 index 0000000000..3fd12ececc --- /dev/null +++ b/components/esp_hid/src/nimble_hidh.c @@ -0,0 +1,1020 @@ +/* + * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ble_hidh.h" +#include "esp_hidh_private.h" +#include "esp_err.h" +#include "esp_log.h" + +#include "esp_hid_common.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include +#include +#include +#include "nimble/nimble_opt.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "host/ble_hs_hci.h" +#include "host/ble_att.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#include "services/bas/ble_svc_bas.h" +#include "services/hid/ble_svc_hid.h" +#include "services/dis/ble_svc_dis.h" + +#if CONFIG_BT_NIMBLE_HID_SERVICE + +#define ESP_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define ESP_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] + +static const char *TAG = "NIMBLE_HIDH"; +static SemaphoreHandle_t s_ble_hidh_cb_semaphore = NULL; + +/* variables used for attribute discovery */ +static int services_discovered; +static int chrs_discovered; +static int dscs_discovered; +static int status = 0; + +static inline void WAIT_CB(void) +{ + xSemaphoreTake(s_ble_hidh_cb_semaphore, portMAX_DELAY); +} + +static inline void SEND_CB(void) +{ + xSemaphoreGive(s_ble_hidh_cb_semaphore); +} + +static esp_event_loop_handle_t event_loop_handle; +static uint8_t *s_read_data_val = NULL; +static uint16_t s_read_data_len = 0; +static int s_read_status = 0; +static esp_event_handler_t s_event_callback; + +/** + * Utility function to log an array of bytes. + */ +void +print_bytes(const uint8_t *bytes, int len) +{ + int i; + + for (i = 0; i < len; i++) { + MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]); + } +} + +static void +print_mbuf(const struct os_mbuf *om) +{ + int colon; + + colon = 0; + while (om != NULL) { + if (colon) { + MODLOG_DFLT(DEBUG, ":"); + } else { + colon = 1; + } + print_bytes(om->om_data, om->om_len); + om = SLIST_NEXT(om, om_next); + } +} + +static int +nimble_on_read(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + int old_offset; + MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error->status, + conn_handle); + s_read_status = error->status; + switch(s_read_status) { + case 0: + MODLOG_DFLT(DEBUG, " attr_handle=%d value=", attr->handle); + old_offset = s_read_data_len; + s_read_data_len += OS_MBUF_PKTLEN(attr->om); + s_read_data_val = realloc(s_read_data_val, s_read_data_len + 1); // 1 extra byte to store null char + ble_hs_mbuf_to_flat(attr->om, s_read_data_val + old_offset, OS_MBUF_PKTLEN(attr->om), NULL); + print_mbuf(attr->om); + return 0; + case BLE_HS_EDONE: + s_read_data_val[s_read_data_len] = 0; // to insure strings are ended with \0 */ + s_read_status = 0; + SEND_CB(); + return 0; + } + return 0; +} + +static int read_char(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len) +{ + s_read_data_val = NULL; + s_read_data_len = 0; + int rc; + + /* read long because the server may not support the large enough mtu */ + rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "read_char failed"); + return rc; + } + WAIT_CB(); + if (s_read_status == 0) { + *out = s_read_data_val; + *out_len = s_read_data_len; + } + return s_read_status; +} + +static int read_descr(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len) +{ + int rc; + + s_read_data_val = NULL; + s_read_data_len = 0; + + rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "read_descr failed"); + return rc; + } + WAIT_CB(); + if (s_read_status == 0) { + *out = s_read_data_val; + *out_len = s_read_data_len; + } + return s_read_status; +} + +static int +svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_svc *service, void *arg) +{ + int rc; + struct ble_gatt_svc *service_result; + uint16_t uuid16; + esp_hidh_dev_t *dev; + + service_result = arg; + rc = 0; + status = error->status; + switch (error->status) { + case 0: + memcpy(service_result + services_discovered, service, sizeof(struct ble_gatt_svc)); + services_discovered++; + uuid16 = ble_uuid_u16(&service->uuid.u); + dev = esp_hidh_dev_get_by_conn_id(conn_handle); + if (!dev) { + ESP_LOGE(TAG, "Service discovery received for unknown device"); + break; + } + if (uuid16 == BLE_SVC_HID_UUID16) { + dev->status = 0; + dev->config.report_maps_len++; + ESP_LOGD(TAG, "HID Service is Discovered"); + } + break; + + case BLE_HS_EDONE: + rc = 0; + status = 0; + /* release the sem now */ + SEND_CB(); + break; + + default: + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + SEND_CB(); + ESP_LOGE(TAG, "Error in service discovery %d\n", rc); + } + + return rc; +} + +static int +chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + const struct ble_gatt_chr *chr, void *arg) +{ + struct ble_gatt_chr *chrs; + int rc; + rc = 0; + chrs = arg; + + status = error->status; + switch (error->status) { + case 0: + ESP_LOGD(TAG,"Char discovered : def handle : %04x, val_handle : %04x, properties : %02x\n, uuid : %04x", + chr->def_handle, chr->val_handle,chr->properties, ble_uuid_u16(&chr->uuid.u)); + memcpy(chrs + chrs_discovered, chr, sizeof(struct ble_gatt_chr)); + chrs_discovered++; + break; + + case BLE_HS_EDONE: + status = 0; + /* release when discovery is complete */ + SEND_CB(); + rc = 0; + break; + + default: + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + SEND_CB(); + } + + return rc; +} + +static int +desc_disced(uint16_t conn_handle, const struct ble_gatt_error *error, + uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc, + void *arg) +{ + int rc; + struct ble_gatt_dsc *dscr; + dscr = arg; + + rc = 0; + status = error->status; + switch (error->status) { + case 0: + ESP_LOGD(TAG,"DISC discovered : handle : %04x, uuid : %04x", + dsc->handle, ble_uuid_u16(&dsc->uuid.u)); + memcpy(dscr + dscs_discovered, dsc, sizeof(struct ble_gatt_dsc)); + dscs_discovered++; + break; + + case BLE_HS_EDONE: + /* All descriptors in this characteristic discovered */ + rc = 0; + status = 0; + /* release the sem as the discovery is complete */ + SEND_CB(); + break; + + default: + /* Error; abort discovery. */ + rc = error->status; + break; + } + + if (rc != 0) { + /* Error; abort discovery. */ + SEND_CB(); + } + + return rc; +} + +/* this api does the following things : +** does service, characteristic and discriptor discovery and +** fills the hid device information accordingly in dev */ +static void read_device_services(esp_hidh_dev_t *dev) +{ + uint16_t suuid, cuuid, duuid; + uint16_t chandle, dhandle; + esp_hidh_dev_report_t *report = NULL; + uint8_t *rdata = 0; + uint16_t rlen = 0; + esp_hid_report_item_t *r; + esp_hid_report_map_t *map; + + struct ble_gatt_svc service_result[10]; + uint16_t dcount = 10; + uint8_t hidindex = 0; + int rc; + + rc = ble_gattc_disc_all_svcs(dev->ble.conn_id, svc_disced, service_result); + if(rc != 0) { + ESP_LOGD(TAG, "Error discovering services : %d", rc); + assert(rc != 0); + } + WAIT_CB(); + if(status != 0) { + ESP_LOGE(TAG, "failed to find services"); + assert(status == 0); + } + dcount = services_discovered; /* fatal if services are more than 10 */ + + if (rc == ESP_OK) { + ESP_LOGD(TAG, "Found %u HID Services", dev->config.report_maps_len); + dev->config.report_maps = (esp_hid_raw_report_map_t *)malloc(dev->config.report_maps_len * sizeof(esp_hid_raw_report_map_t)); + if (dev->config.report_maps == NULL) { + ESP_LOGE(TAG, "malloc report maps failed"); + return; + } + dev->protocol_mode = (uint8_t *)malloc(dev->config.report_maps_len * sizeof(uint8_t)); + if (dev->protocol_mode == NULL) { + ESP_LOGE(TAG, "malloc protocol_mode failed"); + return; + } + + /* read characteristic value may fail, so we should init report maps */ + memset(dev->config.report_maps, 0, dev->config.report_maps_len * sizeof(esp_hid_raw_report_map_t)); + + for (uint16_t s = 0; s < dcount; s++) { + suuid = ble_uuid_u16(&service_result[s].uuid.u); + ESP_LOGD(TAG, "Service discovered : start_handle : %d, end handle : %d, uuid: 0x%04x", + service_result[s].start_handle, service_result[s].end_handle, suuid); + + if (suuid != BLE_SVC_BAS_UUID16 + && suuid != BLE_SVC_DIS_UUID16 + && suuid != BLE_SVC_HID_UUID16 + && suuid != 0x1800) {//device name? + continue; + } + + struct ble_gatt_chr char_result[20]; + uint16_t ccount = 20; + rc = ble_gattc_disc_all_chrs(dev->ble.conn_id, service_result[s].start_handle, + service_result[s].end_handle, chr_disced, char_result); + WAIT_CB(); + if(status != 0) { + ESP_LOGE(TAG, "failed to find chars for service : %d",s); + assert(status == 0); + } + ccount = chrs_discovered; + if (rc == ESP_OK) { + for (uint16_t c = 0; c < ccount; c++) { + cuuid = ble_uuid_u16(&char_result[c].uuid.u); + chandle = char_result[c].val_handle; + ESP_LOGD(TAG, " CHAR:(%d), handle: %d, perm: 0x%02x, uuid: 0x%04x", + c + 1, chandle, char_result[c].properties, cuuid); + + if (suuid == BLE_SVC_GAP_UUID16) { + if (dev->config.device_name == NULL && cuuid == BLE_SVC_GAP_CHR_UUID16_DEVICE_NAME + && (char_result[c].properties & BLE_GATT_CHR_PROP_READ) != 0) { + if (read_char(dev->ble.conn_id, chandle,&rdata, &rlen) == 0 && rlen) { + dev->config.device_name = (const char *)rdata; + } + } else { + continue; + } + } else if (suuid == BLE_SVC_BAS_UUID16) { + if (cuuid == BLE_SVC_BAS_CHR_UUID16_BATTERY_LEVEL && + (char_result[c].properties & BLE_GATT_CHR_PROP_READ) != 0) { + dev->ble.battery_handle = chandle; + } else { + continue; + } + } else if (suuid == BLE_SVC_DIS_UUID16) { + if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) { + if (cuuid == BLE_SVC_DIS_CHR_UUID16_PNP_ID) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen == 7) { + dev->config.vendor_id = *((uint16_t *)&rdata[1]); + dev->config.product_id = *((uint16_t *)&rdata[3]); + dev->config.version = *((uint16_t *)&rdata[5]); + } + free(rdata); + } else if (cuuid == BLE_SVC_DIS_CHR_UUID16_MANUFACTURER_NAME) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { + dev->config.manufacturer_name = (const char *)rdata; + } + } else if (cuuid == BLE_SVC_DIS_CHR_UUID16_SERIAL_NUMBER) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { + dev->config.serial_number = (const char *)rdata; + } + } + } + continue; + } else { + if(cuuid == BLE_SVC_HID_CHR_UUID16_PROTOCOL_MODE) { + if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { + dev->protocol_mode[hidindex] = *((uint8_t *)rdata); + } + } + continue; + } + if (cuuid == BLE_SVC_HID_CHR_UUID16_REPORT_MAP) { + if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) { + if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { + dev->config.report_maps[hidindex].data = (const uint8_t *)rdata; + dev->config.report_maps[hidindex].len = rlen; + } + } + continue; + } else if (cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_INP || cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_OUT + || cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_MOUSE_INP || cuuid == BLE_SVC_HID_CHR_UUID16_RPT) { + report = (esp_hidh_dev_report_t *)malloc(sizeof(esp_hidh_dev_report_t)); + if (report == NULL) { + ESP_LOGE(TAG, "malloc esp_hidh_dev_report_t failed"); + return; + } + report->next = NULL; + report->permissions = char_result[c].properties; + report->handle = chandle; + report->ccc_handle = 0; + report->report_id = 0; + report->map_index = hidindex; + if (cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_INP) { + report->protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + report->report_type = ESP_HID_REPORT_TYPE_INPUT; + report->usage = ESP_HID_USAGE_KEYBOARD; + report->value_len = 8; + } else if (cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_OUT) { + report->protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + report->report_type = ESP_HID_REPORT_TYPE_OUTPUT; + report->usage = ESP_HID_USAGE_KEYBOARD; + report->value_len = 8; + } else if (cuuid == BLE_SVC_HID_CHR_UUID16_BOOT_MOUSE_INP) { + report->protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; + report->report_type = ESP_HID_REPORT_TYPE_INPUT; + report->usage = ESP_HID_USAGE_MOUSE; + report->value_len = 8; + } else { + report->protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT; + report->report_type = 0; + report->usage = ESP_HID_USAGE_GENERIC; + report->value_len = 0; + } + } else { + continue; + } + } + struct ble_gatt_dsc descr_result[20]; + uint16_t dcount = 20; + uint16_t chr_end_handle; + if(c + 1 < ccount) { + chr_end_handle = char_result[c + 1].def_handle; + } + else { + chr_end_handle = service_result[s].end_handle; + } + rc = ble_gattc_disc_all_dscs(dev->ble.conn_id, char_result[c].val_handle, + chr_end_handle, desc_disced, descr_result); + WAIT_CB(); + if(status != 0) { + ESP_LOGE(TAG, "failed to find discriptors for characteristic : %d",c); + assert(status == 0); + } + dcount = dscs_discovered; + if (rc == ESP_OK) { + for (uint16_t d = 0; d < dcount; d++) { + duuid = ble_uuid_u16(&descr_result[d].uuid.u); + dhandle = descr_result[d].handle; + ESP_LOGD(TAG, " DESCR:(%d), handle: %d, uuid: 0x%04x", d + 1, dhandle, duuid); + + if (suuid == BLE_SVC_BAS_UUID16) { + if (duuid == BLE_GATT_DSC_CLT_CFG_UUID16 && + (char_result[c].properties & BLE_GATT_CHR_PROP_NOTIFY) != 0) { + dev->ble.battery_ccc_handle = dhandle; + } + } else if (suuid == BLE_SVC_HID_UUID16 && report != NULL) { + if (duuid == BLE_GATT_DSC_CLT_CFG_UUID16 && (report->permissions & BLE_GATT_CHR_PROP_NOTIFY) != 0) { + report->ccc_handle = dhandle; + } else if (duuid == BLE_SVC_HID_DSC_UUID16_RPT_REF) { + if (read_descr(dev->ble.conn_id, dhandle, &rdata, &rlen) == 0 && rlen) { + report->report_id = rdata[0]; + report->report_type = rdata[1]; + free(rdata); + } + } + } + } + } + if (suuid == BLE_SVC_HID_UUID16 && report != NULL) { + report->next = dev->reports; + dev->reports = report; + dev->reports_len++; + } + dscs_discovered = 0; + } + if (suuid == BLE_SVC_HID_UUID16) { + hidindex++; + } + } + chrs_discovered = 0; // reset the chars array for the next service + } + + for (uint8_t d = 0; d < dev->config.report_maps_len; d++) { + if (dev->reports_len && dev->config.report_maps[d].len) { + map = esp_hid_parse_report_map(dev->config.report_maps[d].data, dev->config.report_maps[d].len); + if (map) { + if (dev->ble.appearance == 0) { + dev->ble.appearance = map->appearance; + } + report = dev->reports; + + while (report) { + if (report->map_index == d) { + for (uint8_t i = 0; i < map->reports_len; i++) { + r = &map->reports[i]; + if (report->protocol_mode == ESP_HID_PROTOCOL_MODE_BOOT + && report->protocol_mode == r->protocol_mode + && report->report_type == r->report_type + && report->usage == r->usage) { + report->report_id = r->report_id; + report->value_len = r->value_len; + } else if (report->protocol_mode == r->protocol_mode + && report->report_type == r->report_type + && report->report_id == r->report_id) { + report->usage = r->usage; + report->value_len = r->value_len; + } + } + } + report = report->next; + } + free(map->reports); + free(map); + map = NULL; + } + } + } + } +} + +static int +on_subscribe(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + uint16_t conn_id; + conn_id =*((uint16_t*) arg); + + assert(conn_id == conn_handle); + + MODLOG_DFLT(INFO, "Subscribe complete; status=%d conn_handle=%d " + "attr_handle=%d\n", + error->status, conn_handle, attr->handle); + SEND_CB(); + + return 0; +} + +static void register_for_notify(uint16_t conn_handle, uint16_t handle) +{ + uint8_t value[2]; + int rc; + value[0] = 1; + value[1] = 0; + rc = ble_gattc_write_flat(conn_handle, handle, value, sizeof value, on_subscribe,(void *)&conn_handle); + if (rc != 0) { + ESP_LOGE(TAG, "Error: Failed to subscribe to characteristic; " + "rc=%d\n", rc); + } + WAIT_CB(); +} + +static int +on_write(uint16_t conn_handle, + const struct ble_gatt_error *error, + struct ble_gatt_attr *attr, + void *arg) +{ + uint16_t conn_id; + conn_id =*((uint16_t*) arg); + + assert(conn_id == conn_handle); + + MODLOG_DFLT(DEBUG, "write complete; status=%d conn_handle=%d " + "attr_handle=%d\n", + error->status, conn_handle, attr->handle); + SEND_CB(); + + return 0; +} +static void write_char_descr(uint16_t conn_id, uint16_t handle, uint16_t value_len, uint8_t *value) +{ + ble_gattc_write_flat(conn_id, handle, value, value_len, on_write, &conn_id); + WAIT_CB(); +} + +static void attach_report_listeners(esp_hidh_dev_t *dev) +{ + if (dev == NULL) { + return; + } + uint16_t ccc_data = 1; + esp_hidh_dev_report_t *report = dev->reports; + + //subscribe to battery notifications + if (dev->ble.battery_handle) { + register_for_notify(dev->ble.conn_id, dev->ble.battery_handle); + if (dev->ble.battery_ccc_handle) { + //Write CCC descr to enable notifications + write_char_descr(dev->ble.conn_id, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data); + } + } + + while (report) { + /* subscribe to notifications */ + if ((report->permissions & BLE_GATT_CHR_PROP_NOTIFY) != 0 && report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) { + register_for_notify(dev->ble.conn_id, report->handle); + if (report->ccc_handle) { + /* Write CCC descr to enable notifications */ + write_char_descr(dev->ble.conn_id, report->ccc_handle, 2, (uint8_t *)&ccc_data); + } + } + report = report->next; + } +} + +static int +esp_hidh_gattc_event_handler(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + esp_hidh_dev_t *dev = NULL; + esp_hidh_dev_report_t *report = NULL; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + if (event->connect.status == 0) { + /* Connection successfully established. */ + MODLOG_DFLT(INFO, "Connection established "); + + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + dev = esp_hidh_dev_get_by_bda(desc.peer_ota_addr.val); + if(!dev) { + ESP_LOGE(TAG, "Connect received for unknown device"); + } + dev->status = -1; // set to not found and clear if HID service is found + dev->ble.conn_id = event->connect.conn_handle; + + /* Try to set the mtu to the max value */ + rc = ble_att_set_preferred_mtu(BLE_ATT_MTU_MAX); + if(rc != 0) { + ESP_LOGE(TAG, "att preferred mtu set failed"); + } + rc = ble_gattc_exchange_mtu(event->connect.conn_handle, NULL, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Failed to negotiate MTU; rc = %d", rc); + } + SEND_CB(); + } else { + MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n", + event->connect.status); + dev->status = event->connect.status; // ESP_GATT_CONN_FAIL_ESTABLISH; + dev->ble.conn_id = -1; + SEND_CB(); // return from connection + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + /* Connection terminated. */ + MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason); + dev = esp_hidh_dev_get_by_conn_id(event->disconnect.conn.conn_handle); + if (!dev) { + ESP_LOGE(TAG, "CLOSE received for unknown device"); + break; + } + if (!dev->connected) { + dev->status = event->disconnect.reason; + dev->ble.conn_id = -1; + } else { + dev->connected = false; + dev->status = event->disconnect.reason; + // free the device in the wrapper event handler + dev->in_use = false; + if (event_loop_handle) { + esp_hidh_event_data_t p = {0}; + p.close.dev = dev; + p.close.reason = event->disconnect.reason; + p.close.status = ESP_OK; + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_CLOSE_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY); + } else { + esp_hidh_dev_free_inner(dev); + } + } + + return 0; + + case BLE_GAP_EVENT_NOTIFY_RX: + /* Peer sent us a notification or indication. */ + MODLOG_DFLT(DEBUG, "received %s; conn_handle=%d attr_handle=%d " + "attr_len=%d\n", + event->notify_rx.indication ? + "indication" : + "notification", + event->notify_rx.conn_handle, + event->notify_rx.attr_handle, + OS_MBUF_PKTLEN(event->notify_rx.om)); + + /* Attribute data is contained in event->notify_rx.om. Use + * `os_mbuf_copydata` to copy the data received in notification mbuf */ + + dev = esp_hidh_dev_get_by_conn_id(event->notify_rx.conn_handle); + if (!dev) { + ESP_LOGE(TAG, "NOTIFY received for unknown device"); + break; + } + if (event_loop_handle) { + esp_hidh_event_data_t p = {0}; + if (event->notify_rx.attr_handle == dev->ble.battery_handle) { + p.battery.dev = dev; + ble_hs_mbuf_to_flat(event->notify_rx.om, &p.battery.level, sizeof(p.battery.level), NULL); + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_BATTERY_EVENT, + &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY); + } else { + report = esp_hidh_dev_get_report_by_handle(dev, event->notify_rx.attr_handle); + if (report) { + esp_hidh_event_data_t *p_param = NULL; + size_t event_data_size = sizeof(esp_hidh_event_data_t); + + if(report->protocol_mode != dev->protocol_mode[report->map_index]) { + /* only pass the notifications in the current protocol mode */ + ESP_LOGD(TAG, "Wrong protocol mode, dropping notification"); + return 0; + } + if (OS_MBUF_PKTLEN(event->notify_rx.om)) { + event_data_size += OS_MBUF_PKTLEN(event->notify_rx.om); + } + + if ((p_param = (esp_hidh_event_data_t *)malloc(event_data_size)) == NULL) { + ESP_LOGE(TAG, "%s malloc event data failed!", __func__); + break; + } + memset(p_param, 0, event_data_size); + if (OS_MBUF_PKTLEN(event->notify_rx.om) && event->notify_rx.om) { + ble_hs_mbuf_to_flat(event->notify_rx.om, ((uint8_t *)p_param) + sizeof(esp_hidh_event_data_t), + OS_MBUF_PKTLEN(event->notify_rx.om), NULL); + } + + if (report->report_type == ESP_HID_REPORT_TYPE_FEATURE) { + p_param->feature.dev = dev; + p_param->feature.map_index = report->map_index; + p_param->feature.report_id = report->report_id; + p_param->feature.usage = report->usage; + p_param->feature.length = OS_MBUF_PKTLEN(event->notify_rx.om); + p_param->feature.data = ((uint8_t *)p_param) + sizeof(esp_hidh_event_data_t); + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_FEATURE_EVENT, p_param, event_data_size, portMAX_DELAY); + } else { + p_param->input.dev = dev; + p_param->input.map_index = report->map_index; + p_param->input.report_id = report->report_id; + p_param->input.usage = report->usage; + p_param->input.length = OS_MBUF_PKTLEN(event->notify_rx.om); + p_param->input.data = ((uint8_t *)p_param) + sizeof(esp_hidh_event_data_t); + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_INPUT_EVENT, p_param, event_data_size, portMAX_DELAY); + } + + if (p_param) { + free(p_param); + p_param = NULL; + } + } + } + } + break; + return 0; + + case BLE_GAP_EVENT_MTU: + MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + default: + return 0; + } + return 0; +} + +/* + * Public Functions + * */ + +static esp_err_t esp_ble_hidh_dev_close(esp_hidh_dev_t *dev) +{ + return ble_gap_terminate(dev->ble.conn_id, BLE_ERR_REM_USER_CONN_TERM); +} + +static esp_err_t esp_ble_hidh_dev_report_write(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, uint8_t *value, size_t value_len) +{ + int rc; + esp_hidh_dev_report_t *report = esp_hidh_dev_get_report_by_id_and_type(dev, map_index, report_id, report_type); + if (!report) { + ESP_LOGE(TAG, "%s report %d not found", esp_hid_report_type_str(report_type), report_id); + return ESP_FAIL; + } + if (value_len > report->value_len) { + ESP_LOGE(TAG, "%s report %d takes maximum %d bytes. you have provided %d", esp_hid_report_type_str(report_type), report_id, report->value_len, value_len); + return ESP_FAIL; + } + rc = ble_gattc_write_flat(dev->ble.conn_id, report->handle, value, value_len, on_write, &dev->ble.conn_id); + WAIT_CB();// this is not blocking in bluedroid code + return rc; +} + +static esp_err_t esp_ble_hidh_dev_report_read(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, size_t max_length, uint8_t *value, size_t *value_len) +{ + esp_hidh_dev_report_t *report = esp_hidh_dev_get_report_by_id_and_type(dev, map_index, report_id, report_type); + if (!report) { + ESP_LOGE(TAG, "%s report %d not found", esp_hid_report_type_str(report_type), report_id); + return ESP_FAIL; + } + uint16_t len = max_length; + uint8_t *v = NULL; + int s = read_char(dev->ble.conn_id, report->handle, &v, &len); + if (s == 0) { + if (len > max_length) { + len = max_length; + } + *value_len = len; + memcpy(value, v, len); + return ESP_OK; + } + ESP_LOGE(TAG, "%s report %d read failed: 0x%x", esp_hid_report_type_str(report_type), report_id, s); + return ESP_FAIL; +} + +static void esp_ble_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp) +{ + fprintf(fp, "BDA:" ESP_BD_ADDR_STR ", Appearance: 0x%04x, Connection ID: %d\n", ESP_BD_ADDR_HEX(dev->bda), + dev->ble.appearance, dev->ble.conn_id); + fprintf(fp, "Name: %s, Manufacturer: %s, Serial Number: %s\n", dev->config.device_name ? dev->config.device_name : "", + dev->config.manufacturer_name ? dev->config.manufacturer_name : "", + dev->config.serial_number ? dev->config.serial_number : ""); + fprintf(fp, "PID: 0x%04x, VID: 0x%04x, VERSION: 0x%04x\n", dev->config.product_id, dev->config.vendor_id, dev->config.version); + fprintf(fp, "Battery: Handle: %u, CCC Handle: %u\n", dev->ble.battery_handle, dev->ble.battery_ccc_handle); + fprintf(fp, "Report Maps: %d\n", dev->config.report_maps_len); + for (uint8_t d = 0; d < dev->config.report_maps_len; d++) { + fprintf(fp, " Report Map Length: %d\n", dev->config.report_maps[d].len); + esp_hidh_dev_report_t *report = dev->reports; + while (report) { + if (report->map_index == d) { + fprintf(fp, " %8s %7s %6s, ID: %2u, Length: %3u, Permissions: 0x%02x, Handle: %3u, CCC Handle: %3u\n", + esp_hid_usage_str(report->usage), esp_hid_report_type_str(report->report_type), + esp_hid_protocol_mode_str(report->protocol_mode), report->report_id, report->value_len, + report->permissions, report->handle, report->ccc_handle); + } + report = report->next; + } + } + +} + +static void esp_ble_hidh_event_handler_wrapper(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, + void *event_data) +{ + esp_hidh_preprocess_event_handler(event_handler_arg, event_base, event_id, event_data); + + if (s_event_callback) { + s_event_callback(event_handler_arg, event_base, event_id, event_data); + } + + esp_hidh_post_process_event_handler(event_handler_arg, event_base, event_id, event_data); +} + +static void nimble_host_synced(void) { +/* + no need to perform anything here +*/ +} + +static void nimble_host_reset(int reason) +{ + MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); +} + +esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config) +{ + esp_err_t ret; + if (config == NULL) { + ESP_LOGE(TAG, "Config is NULL"); + return ESP_FAIL; + } + if (s_ble_hidh_cb_semaphore != NULL) { + ESP_LOGE(TAG, "Already initialised"); + return ESP_FAIL; + } + s_ble_hidh_cb_semaphore = xSemaphoreCreateBinary(); + if (s_ble_hidh_cb_semaphore == NULL) { + ESP_LOGE(TAG, "xSemaphoreCreateMutex failed!"); + return ESP_FAIL; + } + esp_event_loop_args_t event_task_args = { + .queue_size = 5, + .task_name = "esp_ble_hidh_events", + .task_priority = uxTaskPriorityGet(NULL), + .task_stack_size = config->event_stack_size > 0 ? config->event_stack_size : 2048, + .task_core_id = tskNO_AFFINITY + }; + + do { + ret = esp_event_loop_create(&event_task_args, &event_loop_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "%s esp_event_loop_create failed!", __func__); + break; + } + + s_event_callback = config->callback; + ret = esp_event_handler_register_with(event_loop_handle, ESP_HIDH_EVENTS, ESP_EVENT_ANY_ID, + esp_ble_hidh_event_handler_wrapper, config->callback_arg); + } while (0); + + if (ret != ESP_OK) { + if (event_loop_handle) { + esp_event_loop_delete(event_loop_handle); + } + + if (s_ble_hidh_cb_semaphore) { + vSemaphoreDelete(s_ble_hidh_cb_semaphore); + s_ble_hidh_cb_semaphore = NULL; + } + } + + ble_hs_cfg.reset_cb = nimble_host_reset; + ble_hs_cfg.sync_cb = nimble_host_synced; + return ret; +} + +esp_err_t esp_ble_hidh_deinit(void) +{ + if (s_ble_hidh_cb_semaphore == NULL) { + ESP_LOGE(TAG, "Already deinitialised"); + return ESP_FAIL; + } + + if (event_loop_handle) { + esp_event_loop_delete(event_loop_handle); + } + vSemaphoreDelete(s_ble_hidh_cb_semaphore); + s_ble_hidh_cb_semaphore = NULL; + s_event_callback = NULL; + + return 0; +} + +esp_hidh_dev_t *esp_ble_hidh_dev_open(uint8_t *bda, uint8_t address_type) +{ + esp_err_t ret; + ble_addr_t addr; + uint8_t own_addr_type; + + own_addr_type = 0; // set to public for now + esp_hidh_dev_t *dev = esp_hidh_dev_malloc(); + if (dev == NULL) { + ESP_LOGE(TAG, "malloc esp_hidh_dev_t failed"); + return NULL; + } + + dev->in_use = true; + dev->transport = ESP_HID_TRANSPORT_BLE; + memcpy(dev->bda, bda, sizeof(dev->bda)); + dev->ble.address_type = address_type; + dev->ble.appearance = ESP_HID_APPEARANCE_GENERIC; + + memcpy(addr.val, bda, sizeof(addr.val)); + addr.type = address_type; + + ret = ble_gap_connect(own_addr_type, &addr, 30000, NULL, esp_hidh_gattc_event_handler, NULL); + if (ret) { + esp_hidh_dev_free_inner(dev); + ESP_LOGE(TAG, "esp_ble_gattc_open failed: %d", ret); + return NULL; + } + WAIT_CB(); + if (dev->ble.conn_id < 0) { + ret = dev->status; + ESP_LOGE(TAG, "dev open failed! status: 0x%x", dev->status); + esp_hidh_dev_free_inner(dev); + return NULL; + } + + dev->close = esp_ble_hidh_dev_close; + dev->report_write = esp_ble_hidh_dev_report_write; + dev->report_read = esp_ble_hidh_dev_report_read; + dev->dump = esp_ble_hidh_dev_dump; + + /* perform service discovery and fill the report maps */ + read_device_services(dev); + + if (event_loop_handle) { + esp_hidh_event_data_t p = {0}; + p.open.status = ESP_OK; + p.open.dev = dev; + esp_event_post_to(event_loop_handle, ESP_HIDH_EVENTS, ESP_HIDH_OPEN_EVENT, &p, sizeof(esp_hidh_event_data_t), portMAX_DELAY); + } + + attach_report_listeners(dev); + return dev; +} +#endif // CONFIG_BT_NIMBLE_HID_SERVICE diff --git a/examples/bluetooth/esp_hid_device/main/Kconfig.projbuild b/examples/bluetooth/esp_hid_device/main/Kconfig.projbuild new file mode 100644 index 0000000000..37da169d7b --- /dev/null +++ b/examples/bluetooth/esp_hid_device/main/Kconfig.projbuild @@ -0,0 +1,38 @@ +menu "HID Example Configuration" + config EXAMPLE_SSP_ENABLED + bool "Secure Simple Pairing" + depends on BT_CLASSIC_ENABLED + default y + help + This enables the Secure Simple Pairing. If disable this option, + Bluedroid will only support Legacy Pairing + + choice EXAMPLE_HID_DEVICE_ROLE + prompt "HID Device Role" + depends on BT_NIMBLE_ENABLED + default EXAMPLE_MEDIA_ENABLE + help + Three Supported Roles for Device + - Media Device + - Keyboard + - Mouse + + config EXAMPLE_MEDIA_ENABLE + select BT_NIMBLE_HID_SERVICE + bool "Enable Media Device" + + config EXAMPLE_KBD_ENABLE + select BT_NIMBLE_HID_SERVICE + bool "Enable Keyboard Device" + + config EXAMPLE_MOUSE_ENABLE + select BT_NIMBLE_HID_SERVICE + bool "Enable Mouse Device" + endchoice + + config EXAMPLE_HID_DEVICE_ROLE + int + default 1 if EXAMPLE_MEDIA_ENABLE + default 2 if EXAMPLE_KBD_ENABLE + default 3 if EXAMPLE_MOUSE_ENABLE +endmenu diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c index ae771fc85f..7b36592483 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_device_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -18,6 +18,12 @@ #include "esp_log.h" #include "nvs_flash.h" #include "esp_bt.h" + +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#else #include "esp_bt_defs.h" #if CONFIG_BT_BLE_ENABLED #include "esp_gap_ble_api.h" @@ -26,6 +32,7 @@ #endif #include "esp_bt_main.h" #include "esp_bt_device.h" +#endif #include "esp_hidd.h" #include "esp_hid_gap.h" @@ -40,7 +47,7 @@ typedef struct uint8_t *buffer; } local_param_t; -#if CONFIG_BT_BLE_ENABLED +#if CONFIG_BT_BLE_ENABLED || CONFIG_BT_NIMBLE_ENABLED static local_param_t s_ble_hid_param = {0}; const unsigned char mediaReportMap[] = { @@ -102,19 +109,287 @@ const unsigned char mediaReportMap[] = { 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, // End Collection }; +#if CONFIG_EXAMPLE_HID_DEVICE_ROLE && CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 +const unsigned char mouseReportMap[] = { + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xa1, 0x01, // COLLECTION (Application) + 0x09, 0x01, // USAGE (Pointer) + 0xa1, 0x00, // COLLECTION (Physical) + + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x03, // USAGE_MAXIMUM (Button 3) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x03, // REPORT_COUNT (3) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x05, // REPORT_SIZE (5) + 0x81, 0x03, // INPUT (Cnst,Var,Abs) + + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x09, 0x38, // USAGE (Wheel) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x03, // REPORT_COUNT (3) + 0x81, 0x06, // INPUT (Data,Var,Rel) + + 0xc0, // END_COLLECTION + 0xc0 // END_COLLECTION +}; +// send the buttons, change in x, and change in y +void send_mouse(uint8_t buttons, char dx, char dy, char wheel) +{ + static uint8_t buffer[4] = {0}; + buffer[0] = buttons; + buffer[1] = dx; + buffer[2] = dy; + buffer[3] = wheel; + esp_hidd_dev_input_set(s_ble_hid_param.hid_dev, 0, 0, buffer, 4); +} + +void ble_hid_demo_task_mouse(void *pvParameters) +{ + static const char* help_string = "########################################################################\n"\ + "BT hid mouse demo usage:\n"\ + "You can input these value to simulate mouse: 'q', 'w', 'e', 'a', 's', 'd', 'h'\n"\ + "q -- click the left key\n"\ + "w -- move up\n"\ + "e -- click the right key\n"\ + "a -- move left\n"\ + "s -- move down\n"\ + "d -- move right\n"\ + "h -- show the help\n"\ + "########################################################################\n"; + printf("%s\n", help_string); + char c; + while (1) { + c = fgetc(stdin); + switch (c) { + case 'q': + send_mouse(1, 0, 0, 0); + break; + case 'w': + send_mouse(0, 0, -10, 0); + break; + case 'e': + send_mouse(2, 0, 0, 0); + break; + case 'a': + send_mouse(0, -10, 0, 0); + break; + case 's': + send_mouse(0, 0, 10, 0); + break; + case 'd': + send_mouse(0, 10, 0, 0); + break; + case 'h': + printf("%s\n", help_string); + break; + default: + break; + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} +#endif + +#if CONFIG_EXAMPLE_HID_DEVICE_ROLE && CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 +#define CASE(a, b, c) \ + case a: \ + buffer[0] = b; \ + buffer[2] = c; \ + break;\ + +// USB keyboard codes +#define USB_HID_MODIFIER_LEFT_CTRL 0x01 +#define USB_HID_MODIFIER_LEFT_SHIFT 0x02 +#define USB_HID_MODIFIER_LEFT_ALT 0x04 +#define USB_HID_MODIFIER_RIGHT_CTRL 0x10 +#define USB_HID_MODIFIER_RIGHT_SHIFT 0x20 +#define USB_HID_MODIFIER_RIGHT_ALT 0x40 + +#define USB_HID_SPACE 0x2C +#define USB_HID_DOT 0x37 +#define USB_HID_NEWLINE 0x28 +#define USB_HID_FSLASH 0x38 +#define USB_HID_BSLASH 0x31 +#define USB_HID_COMMA 0x36 +#define USB_HID_DOT 0x37 + +const unsigned char keyboardReportMap[] = { //7 bytes input (modifiers, resrvd, keys*5), 1 byte output + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x06, // Usage (Keyboard) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x01, // Report ID (1) + 0x05, 0x07, // Usage Page (Kbrd/Keypad) + 0x19, 0xE0, // Usage Minimum (0xE0) + 0x29, 0xE7, // Usage Maximum (0xE7) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x95, 0x01, // Report Count (1) + 0x75, 0x08, // Report Size (8) + 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x05, 0x08, // Usage Page (LEDs) + 0x19, 0x01, // Usage Minimum (Num Lock) + 0x29, 0x05, // Usage Maximum (Kana) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x95, 0x01, // Report Count (1) + 0x75, 0x03, // Report Size (3) + 0x91, 0x03, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x95, 0x05, // Report Count (5) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x65, // Logical Maximum (101) + 0x05, 0x07, // Usage Page (Kbrd/Keypad) + 0x19, 0x00, // Usage Minimum (0x00) + 0x29, 0x65, // Usage Maximum (0x65) + 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + + // 65 bytes +}; + +static void char_to_code(uint8_t *buffer, char ch) +{ + // Check if lower or upper case + if(ch >= 'a' && ch <= 'z') + { + buffer[0] = 0; + // convert ch to HID letter, starting at a = 4 + buffer[2] = (uint8_t)(4 + (ch - 'a')); + } + else if(ch >= 'A' && ch <= 'Z') + { + // Add left shift + buffer[0] = USB_HID_MODIFIER_LEFT_SHIFT; + // convert ch to lower case + ch = ch - ('A'-'a'); + // convert ch to HID letter, starting at a = 4 + buffer[2] = (uint8_t)(4 + (ch - 'a')); + } + else if(ch >= '0' && ch <= '9') // Check if number + { + buffer[0] = 0; + // convert ch to HID number, starting at 1 = 30, 0 = 39 + if(ch == '0') + { + buffer[2] = 39; + } + else + { + buffer[2] = (uint8_t)(30 + (ch - '1')); + } + } + else // not a letter nor a number + { + switch(ch) + { + CASE(' ', 0, USB_HID_SPACE); + CASE('.', 0,USB_HID_DOT); + CASE('\n', 0, USB_HID_NEWLINE); + CASE('?', USB_HID_MODIFIER_LEFT_SHIFT, USB_HID_FSLASH); + CASE('/', 0 ,USB_HID_FSLASH); + CASE('\\', 0, USB_HID_BSLASH); + CASE('|', USB_HID_MODIFIER_LEFT_SHIFT, USB_HID_BSLASH); + CASE(',', 0, USB_HID_COMMA); + CASE('<', USB_HID_MODIFIER_LEFT_SHIFT, USB_HID_COMMA); + CASE('>', USB_HID_MODIFIER_LEFT_SHIFT, USB_HID_COMMA); + CASE('@', USB_HID_MODIFIER_LEFT_SHIFT, 31); + CASE('!', USB_HID_MODIFIER_LEFT_SHIFT, 30); + CASE('#', USB_HID_MODIFIER_LEFT_SHIFT, 32); + CASE('$', USB_HID_MODIFIER_LEFT_SHIFT, 33); + CASE('%', USB_HID_MODIFIER_LEFT_SHIFT, 34); + CASE('^', USB_HID_MODIFIER_LEFT_SHIFT,35); + CASE('&', USB_HID_MODIFIER_LEFT_SHIFT, 36); + CASE('*', USB_HID_MODIFIER_LEFT_SHIFT, 37); + CASE('(', USB_HID_MODIFIER_LEFT_SHIFT, 38); + CASE(')', USB_HID_MODIFIER_LEFT_SHIFT, 39); + CASE('-', 0, 0x2D); + CASE('_', USB_HID_MODIFIER_LEFT_SHIFT, 0x2D); + CASE('=', 0, 0x2E); + CASE('+', USB_HID_MODIFIER_LEFT_SHIFT, 39); + CASE(8, 0, 0x2A); // backspace + CASE('\t', 0, 0x2B); + default: + buffer[0] = 0; + buffer[2] = 0; + } + } +} + +void send_keyboard(char c) +{ + static uint8_t buffer[8] = {0}; + char_to_code(buffer, c); + esp_hidd_dev_input_set(s_ble_hid_param.hid_dev, 0, 1, buffer, 8); + /* send the keyrelease event with sufficient delay */ + vTaskDelay(50 / portTICK_PERIOD_MS); + memset(buffer, 0, sizeof(uint8_t) * 8); + esp_hidd_dev_input_set(s_ble_hid_param.hid_dev, 0, 1, buffer, 8); +} + +void ble_hid_demo_task_kbd(void *pvParameters) +{ + static const char* help_string = "########################################################################\n"\ + "BT hid keyboard demo usage:\n"\ + "########################################################################\n"; + /* TODO : Add support for function keys and ctrl, alt, esc, etc. */ + printf("%s\n", help_string); + char c; + while (1) { + c = fgetc(stdin); + + if(c != 255) { + send_keyboard(c); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} +#endif static esp_hid_raw_report_map_t ble_report_maps[] = { +#if !CONFIG_BT_NIMBLE_ENABLED || CONFIG_EXAMPLE_HID_DEVICE_ROLE == 1 + /* This block is compiled for bluedroid as well */ { .data = mediaReportMap, .len = sizeof(mediaReportMap) } +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE && CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 + { + .data = keyboardReportMap, + .len = sizeof(keyboardReportMap) + }, +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE && CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 + { + .data = mouseReportMap, + .len = sizeof(mouseReportMap) + }, +#endif }; static esp_hid_device_config_t ble_hid_config = { .vendor_id = 0x16C0, .product_id = 0x05DF, .version = 0x0100, +#if CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 + .device_name = "ESP Keyboard", +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 + .device_name = "ESP Mouse", +#else .device_name = "ESP BLE HID2", +#endif .manufacturer_name = "Espressif", .serial_number = "1234567890", .report_maps = ble_report_maps, @@ -272,6 +547,7 @@ void esp_hidd_send_consumer_value(uint8_t key_cmd, bool key_pressed) return; } +#if !CONFIG_BT_NIMBLE_ENABLED || CONFIG_EXAMPLE_HID_DEVICE_ROLE == 1 void ble_hid_demo_task(void *pvParameters) { static bool send_volum_up = false; @@ -290,6 +566,7 @@ void ble_hid_demo_task(void *pvParameters) vTaskDelay(2000 / portTICK_PERIOD_MS); } } +#endif void ble_hid_task_start_up(void) { @@ -297,8 +574,23 @@ void ble_hid_task_start_up(void) // Task already exists return; } +#if !CONFIG_BT_NIMBLE_ENABLED + /* Executed for bluedroid */ xTaskCreate(ble_hid_demo_task, "ble_hid_demo_task", 2 * 1024, NULL, configMAX_PRIORITIES - 3, &s_ble_hid_param.task_hdl); +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 1 + xTaskCreate(ble_hid_demo_task, "ble_hid_demo_task", 3 * 1024, NULL, configMAX_PRIORITIES - 3, + &s_ble_hid_param.task_hdl); + +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 + /* Nimble Specific */ + xTaskCreate(ble_hid_demo_task_kbd, "ble_hid_demo_task_kbd", 3 * 1024, NULL, configMAX_PRIORITIES - 3, + &s_ble_hid_param.task_hdl); +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 + /* Nimble Specific */ + xTaskCreate(ble_hid_demo_task_mouse, "ble_hid_demo_task_mouse", 3 * 1024, NULL, configMAX_PRIORITIES - 3, + &s_ble_hid_param.task_hdl); +#endif } void ble_hid_task_shut_down(void) @@ -557,6 +849,18 @@ static void bt_hidd_event_callback(void *handler_args, esp_event_base_t base, in } #endif +#if CONFIG_BT_NIMBLE_ENABLED +void ble_hid_device_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} +void ble_store_config_init(void); +#endif + void app_main(void) { esp_err_t ret; @@ -575,14 +879,21 @@ void app_main(void) ret = esp_hid_gap_init(HID_DEV_MODE); ESP_ERROR_CHECK( ret ); -#if CONFIG_BT_BLE_ENABLED +#if CONFIG_BT_BLE_ENABLED || CONFIG_BT_NIMBLE_ENABLED +#if CONFIG_EXAMPLE_HID_DEVICE_ROLE == 2 + ret = esp_hid_ble_gap_adv_init(ESP_HID_APPEARANCE_KEYBOARD, ble_hid_config.device_name); +#elif CONFIG_EXAMPLE_HID_DEVICE_ROLE == 3 + ret = esp_hid_ble_gap_adv_init(ESP_HID_APPEARANCE_MOUSE, ble_hid_config.device_name); +#else ret = esp_hid_ble_gap_adv_init(ESP_HID_APPEARANCE_GENERIC, ble_hid_config.device_name); +#endif ESP_ERROR_CHECK( ret ); - +#if CONFIG_BT_BLE_ENABLED if ((ret = esp_ble_gatts_register_callback(esp_hidd_gatts_event_handler)) != ESP_OK) { ESP_LOGE(TAG, "GATTS register callback failed: %d", ret); return; } +#endif ESP_LOGI(TAG, "setting ble device"); ESP_ERROR_CHECK( esp_hidd_dev_init(&ble_hid_config, ESP_HID_TRANSPORT_BLE, ble_hidd_event_callback, &s_ble_hid_param.hid_dev)); @@ -601,4 +912,15 @@ void app_main(void) ESP_ERROR_CHECK( esp_hidd_dev_init(&bt_hid_config, ESP_HID_TRANSPORT_BT, bt_hidd_event_callback, &s_bt_hid_param.hid_dev)); #endif +#if CONFIG_BT_NIMBLE_ENABLED + /* XXX Need to have template for store */ + ble_store_config_init(); + + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /* Starting nimble task after gatts is initialized*/ + ret = esp_nimble_enable(ble_hid_device_host_task); + if (ret) { + ESP_LOGE(TAG, "esp_nimble_enable failed: %d", ret); + } +#endif } diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c index 14199000f0..0277de0162 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -15,17 +15,28 @@ #include "esp_hid_gap.h" +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "nimble/ble.h" +#include "host/ble_sm.h" +#endif + static const char *TAG = "ESP_HID_GAP"; // uncomment to print all devices that were seen during a scan #define GAP_DBG_PRINTF(...) //printf(__VA_ARGS__) //static const char * gap_bt_prop_type_names[5] = {"","BDNAME","COD","RSSI","EIR"}; +#if !CONFIG_BT_NIMBLE_ENABLED static esp_hid_scan_result_t *bt_scan_results = NULL; static size_t num_bt_scan_results = 0; static esp_hid_scan_result_t *ble_scan_results = NULL; static size_t num_ble_scan_results = 0; +#endif static SemaphoreHandle_t bt_hidh_cb_semaphore = NULL; #define WAIT_BT_CB() xSemaphoreTake(bt_hidh_cb_semaphore, portMAX_DELAY) @@ -37,6 +48,7 @@ static SemaphoreHandle_t ble_hidh_cb_semaphore = NULL; #define SIZEOF_ARRAY(a) (sizeof(a)/sizeof(*a)) +#if !CONFIG_BT_NIMBLE_ENABLED static const char *ble_gap_evt_names[] = { "ADV_DATA_SET_COMPLETE", "SCAN_RSP_DATA_SET_COMPLETE", "SCAN_PARAM_SET_COMPLETE", "SCAN_RESULT", "ADV_DATA_RAW_SET_COMPLETE", "SCAN_RSP_DATA_RAW_SET_COMPLETE", "ADV_START_COMPLETE", "SCAN_START_COMPLETE", "AUTH_CMPL", "KEY", "SEC_REQ", "PASSKEY_NOTIF", "PASSKEY_REQ", "OOB_REQ", "LOCAL_IR", "LOCAL_ER", "NC_REQ", "ADV_STOP_COMPLETE", "SCAN_STOP_COMPLETE", "SET_STATIC_RAND_ADDR", "UPDATE_CONN_PARAMS", "SET_PKT_LENGTH_COMPLETE", "SET_LOCAL_PRIVACY_COMPLETE", "REMOVE_BOND_DEV_COMPLETE", "CLEAR_BOND_DEV_COMPLETE", "GET_BOND_DEV_COMPLETE", "READ_RSSI_COMPLETE", "UPDATE_WHITELIST_COMPLETE"}; static const char *bt_gap_evt_names[] = { "DISC_RES", "DISC_STATE_CHANGED", "RMT_SRVCS", "RMT_SRVC_REC", "AUTH_CMPL", "PIN_REQ", "CFM_REQ", "KEY_NOTIF", "KEY_REQ", "READ_RSSI_DELTA"}; static const char *ble_addr_type_names[] = {"PUBLIC", "RANDOM", "RPA_PUBLIC", "RPA_RANDOM"}; @@ -64,6 +76,7 @@ const char *bt_gap_evt_str(uint8_t event) } return bt_gap_evt_names[event]; } +#endif #if CONFIG_BT_BLE_ENABLED const char *esp_ble_key_type_str(esp_ble_key_type_t key_type) @@ -106,6 +119,7 @@ const char *esp_ble_key_type_str(esp_ble_key_type_t key_type) } #endif /* CONFIG_BT_BLE_ENABLED */ +#if !CONFIG_BT_NIMBLE_ENABLED void esp_hid_scan_results_free(esp_hid_scan_result_t *results) { esp_hid_scan_result_t *r = NULL; @@ -118,6 +132,7 @@ void esp_hid_scan_results_free(esp_hid_scan_result_t *results) free(r); } } +#endif #if (CONFIG_BT_HID_DEVICE_ENABLED || CONFIG_BT_BLE_ENABLED) static esp_hid_scan_result_t *find_scan_result(esp_bd_addr_t bda, esp_hid_scan_result_t *results) @@ -223,6 +238,7 @@ static void add_ble_scan_result(esp_bd_addr_t bda, esp_ble_addr_type_t addr_type } #endif /* CONFIG_BT_BLE_ENABLED */ +#if !CONFIG_BT_NIMBLE_ENABLED void print_uuid(esp_bt_uuid_t *uuid) { if (uuid->len == ESP_UUID_LEN_16) { @@ -238,6 +254,7 @@ void print_uuid(esp_bt_uuid_t *uuid) uuid->uuid.uuid128[13], uuid->uuid.uuid128[14], uuid->uuid.uuid128[15]); } } +#endif #if CONFIG_BT_HID_DEVICE_ENABLED static void handle_bt_device_result(struct disc_res_param *disc_res) @@ -600,6 +617,7 @@ static esp_err_t start_ble_scan(uint32_t seconds) return ret; } +#if !CONFIG_BT_NIMBLE_ENABLED esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name) { @@ -677,7 +695,7 @@ esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name) return ret; } - +#endif esp_err_t esp_hid_ble_gap_adv_start(void) { static esp_ble_adv_params_t hidd_adv_params = { @@ -691,11 +709,212 @@ esp_err_t esp_hid_ble_gap_adv_start(void) return esp_ble_gap_start_advertising(&hidd_adv_params); } #endif /* CONFIG_BT_BLE_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED +static struct ble_hs_adv_fields fields; +#define GATT_SVR_SVC_HID_UUID 0x1812 +esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name) +{ + ble_uuid16_t *uuid16, *uuid16_1; + /** + * Set the advertisement data included in our advertisements: + * o Flags (indicates advertisement type and other general info). + * o Advertising tx power. + * o Device name. + * o 16-bit service UUIDs (HID). + */ + + memset(&fields, 0, sizeof fields); + + /* Advertise two flags: + * o Discoverability in forthcoming advertisement (general) + * o BLE-only (BR/EDR unsupported). + */ + fields.flags = BLE_HS_ADV_F_DISC_GEN | + BLE_HS_ADV_F_BREDR_UNSUP; + + /* Indicate that the TX power level field should be included; have the + * stack fill this value automatically. This is done by assigning the + * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. + */ + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + + fields.name = (uint8_t *)device_name; + fields.name_len = strlen(device_name); + fields.name_is_complete = 1; + + uuid16 = (ble_uuid16_t *)malloc(sizeof(ble_uuid16_t)); + uuid16_1 = (ble_uuid16_t[]) { + BLE_UUID16_INIT(GATT_SVR_SVC_HID_UUID) + }; + memcpy(uuid16, uuid16_1, sizeof(ble_uuid16_t)); + fields.uuids16 = uuid16; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; + + /* Initialize the security configuration */ + ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_ONLY; + ble_hs_cfg.sm_bonding = 1; + ble_hs_cfg.sm_mitm = 1; + ble_hs_cfg.sm_sc = 1; + ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ID | BLE_SM_PAIR_KEY_DIST_ENC; + ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID | BLE_SM_PAIR_KEY_DIST_ENC; + + return ESP_OK; + +} + +static int +nimble_hid_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + ESP_LOGI(TAG, "connection %s; status=%d", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + return 0; + break; + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "disconnect; reason=%d", event->disconnect.reason); + + return 0; + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + ESP_LOGI(TAG, "connection updated; status=%d", + event->conn_update.status); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + ESP_LOGI(TAG, "advertise complete; reason=%d", + event->adv_complete.reason); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, + event->subscribe.attr_handle, + event->subscribe.reason, + event->subscribe.prev_notify, + event->subscribe.cur_notify, + event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + return 0; + + case BLE_GAP_EVENT_MTU: + ESP_LOGI(TAG, "mtu update event; conn_handle=%d cid=%d mtu=%d", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + MODLOG_DFLT(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + return 0; + + case BLE_GAP_EVENT_NOTIFY_TX: + MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d " + "status=%d is_indication=%d", + event->notify_tx.conn_handle, + event->notify_tx.attr_handle, + event->notify_tx.status, + event->notify_tx.indication); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + ESP_LOGI(TAG, "PASSKEY_ACTION_EVENT started"); + struct ble_sm_io pkey = {0}; + int key = 0; + + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; // This is the passkey to be entered on peer + ESP_LOGI(TAG, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey); + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + ESP_LOGI(TAG, "Accepting passkey.."); + pkey.action = event->passkey.params.action; + pkey.numcmp_accept = key; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + static uint8_t tem_oob[16] = {0}; + pkey.action = event->passkey.params.action; + for (int i = 0; i < 16; i++) { + pkey.oob[i] = tem_oob[i]; + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + ESP_LOGI(TAG, "Input not supported passing -> 123456"); + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } + return 0; + } + return 0; +} +esp_err_t esp_hid_ble_gap_adv_start(void) +{ + int rc; + struct ble_gap_adv_params adv_params; + /* maximum possible duration for hid device(180s) */ + int32_t adv_duration_ms = 180000; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc); + return rc; + } + /* Begin advertising. */ + memset(&adv_params, 0, sizeof adv_params); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(30);/* Recommended interval 30ms to 50ms */ + adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(50); + rc = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, adv_duration_ms, + &adv_params, nimble_hid_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); + return rc; + } + return rc; +} +#endif + /* * CONTROLLER INIT * */ +#if !CONFIG_BT_NIMBLE_ENABLED static esp_err_t init_low_level(uint8_t mode) { esp_err_t ret; @@ -757,6 +976,43 @@ static esp_err_t init_low_level(uint8_t mode) #endif /* CONFIG_BT_BLE_ENABLED */ return ret; } +#endif + +#if CONFIG_BT_NIMBLE_ENABLED +static esp_err_t init_low_level(uint8_t mode) +{ + esp_err_t ret; + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +#if CONFIG_IDF_TARGET_ESP32 + bt_cfg.mode = mode; +#endif + ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (ret) { + ESP_LOGE(TAG, "esp_bt_controller_mem_release failed: %d", ret); + return ret; + } + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "esp_bt_controller_init failed: %d", ret); + return ret; + } + + ret = esp_bt_controller_enable(mode); + if (ret) { + ESP_LOGE(TAG, "esp_bt_controller_enable failed: %d", ret); + return ret; + } + + ret = esp_nimble_init(); + if (ret) { + ESP_LOGE(TAG, "esp_nimble_init failed: %d", ret); + return ret; + } + + + return ret; +} +#endif esp_err_t esp_hid_gap_init(uint8_t mode) { @@ -797,6 +1053,7 @@ esp_err_t esp_hid_gap_init(uint8_t mode) return ESP_OK; } +#if !CONFIG_BT_NIMBLE_ENABLED esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_result_t **results) { if (num_bt_scan_results || bt_scan_results || num_ble_scan_results || ble_scan_results) { @@ -837,3 +1094,4 @@ esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_resul ble_scan_results = NULL; return ESP_OK; } +#endif diff --git a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h index 8e2cf3897b..7399fb7b82 100644 --- a/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h +++ b/examples/bluetooth/esp_hid_device/main/esp_hid_gap.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -20,6 +20,8 @@ #endif #elif CONFIG_BT_BLE_ENABLED #define HID_DEV_MODE HIDD_BLE_MODE +#elif CONFIG_BT_NIMBLE_ENABLED +#define HID_DEV_MODE HIDD_BLE_MODE #else #define HID_DEV_MODE HIDD_IDLE_MODE #endif @@ -28,9 +30,11 @@ #include "esp_log.h" #include "esp_bt.h" +#if !CONFIG_BT_NIMBLE_ENABLED #include "esp_bt_defs.h" #include "esp_bt_main.h" #include "esp_gap_bt_api.h" +#endif #include "esp_hid_common.h" #if CONFIG_BT_BLE_ENABLED #include "esp_gattc_api.h" @@ -42,6 +46,7 @@ extern "C" { #endif +#if !CONFIG_BT_NIMBLE_ENABLED typedef struct esp_hidh_scan_result_s { struct esp_hidh_scan_result_s *next; @@ -62,16 +67,17 @@ typedef struct esp_hidh_scan_result_s { }; } esp_hid_scan_result_t; -esp_err_t esp_hid_gap_init(uint8_t mode); esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_result_t **results); void esp_hid_scan_results_free(esp_hid_scan_result_t *results); +const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type); +void print_uuid(esp_bt_uuid_t *uuid); +#endif + +esp_err_t esp_hid_gap_init(uint8_t mode); esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name); esp_err_t esp_hid_ble_gap_adv_start(void); -void print_uuid(esp_bt_uuid_t *uuid); -const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type); - #ifdef __cplusplus } #endif diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c index a1a700e67c..ca4c3083bb 100644 --- a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c +++ b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -14,6 +14,15 @@ #include "freertos/semphr.h" #include "esp_hid_gap.h" +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "nimble/ble.h" +#include "host/ble_sm.h" +#define BLE_HID_SVC_UUID 0x1812 /* HID Service*/ +#endif static const char *TAG = "ESP_HID_GAP"; @@ -40,6 +49,7 @@ static SemaphoreHandle_t ble_hidh_cb_semaphore = NULL; #define SIZEOF_ARRAY(a) (sizeof(a)/sizeof(*a)) +#if !CONFIG_BT_NIMBLE_ENABLED static const char *ble_gap_evt_names[] = { "ADV_DATA_SET_COMPLETE", "SCAN_RSP_DATA_SET_COMPLETE", "SCAN_PARAM_SET_COMPLETE", "SCAN_RESULT", "ADV_DATA_RAW_SET_COMPLETE", "SCAN_RSP_DATA_RAW_SET_COMPLETE", "ADV_START_COMPLETE", "SCAN_START_COMPLETE", "AUTH_CMPL", "KEY", "SEC_REQ", "PASSKEY_NOTIF", "PASSKEY_REQ", "OOB_REQ", "LOCAL_IR", "LOCAL_ER", "NC_REQ", "ADV_STOP_COMPLETE", "SCAN_STOP_COMPLETE", "SET_STATIC_RAND_ADDR", "UPDATE_CONN_PARAMS", "SET_PKT_LENGTH_COMPLETE", "SET_LOCAL_PRIVACY_COMPLETE", "REMOVE_BOND_DEV_COMPLETE", "CLEAR_BOND_DEV_COMPLETE", "GET_BOND_DEV_COMPLETE", "READ_RSSI_COMPLETE", "UPDATE_WHITELIST_COMPLETE"}; static const char *bt_gap_evt_names[] = { "DISC_RES", "DISC_STATE_CHANGED", "RMT_SRVCS", "RMT_SRVC_REC", "AUTH_CMPL", "PIN_REQ", "CFM_REQ", "KEY_NOTIF", "KEY_REQ", "READ_RSSI_DELTA"}; static const char *ble_addr_type_names[] = {"PUBLIC", "RANDOM", "RPA_PUBLIC", "RPA_RANDOM"}; @@ -67,7 +77,7 @@ const char *bt_gap_evt_str(uint8_t event) } return bt_gap_evt_names[event]; } - +#endif #if CONFIG_BT_BLE_ENABLED const char *esp_ble_key_type_str(esp_ble_key_type_t key_type) { @@ -136,6 +146,19 @@ static esp_hid_scan_result_t *find_scan_result(esp_bd_addr_t bda, esp_hid_scan_r } #endif /* (CONFIG_BT_HID_HOST_ENABLED || CONFIG_BT_BLE_ENABLED) */ +#if (CONFIG_BT_NIMBLE_ENABLED) +static esp_hid_scan_result_t *find_scan_result(const uint8_t *bda, esp_hid_scan_result_t *results) +{ + esp_hid_scan_result_t *r = results; + while (r) { + if (memcmp(bda, r->bda, sizeof(r->bda)) == 0) { + return r; + } + r = r->next; + } + return NULL; +} +#endif #if CONFIG_BT_HID_HOST_ENABLED static void add_bt_scan_result(esp_bd_addr_t bda, esp_bt_cod_t *cod, esp_bt_uuid_t *uuid, uint8_t *name, uint8_t name_len, int rssi) { @@ -226,6 +249,43 @@ static void add_ble_scan_result(esp_bd_addr_t bda, esp_ble_addr_type_t addr_type } #endif /* CONFIG_BT_BLE_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED +static void add_ble_scan_result(const uint8_t *bda, uint8_t addr_type, uint16_t appearance, uint8_t *name, uint8_t name_len, int rssi) +{ + if (find_scan_result(bda, ble_scan_results)) { + ESP_LOGW(TAG, "Result already exists!"); + return; + } + esp_hid_scan_result_t *r = (esp_hid_scan_result_t *)malloc(sizeof(esp_hid_scan_result_t)); + if (r == NULL) { + ESP_LOGE(TAG, "Malloc ble_hidh_scan_result_t failed!"); + return; + } + r->transport = ESP_HID_TRANSPORT_BLE; + memcpy(r->bda, bda, sizeof(r->bda)); + r->ble.appearance = appearance; + r->ble.addr_type = addr_type; + r->usage = esp_hid_usage_from_appearance(appearance); + r->rssi = rssi; + r->name = NULL; + if (name_len && name) { + char *name_s = (char *)malloc(name_len + 1); + if (name_s == NULL) { + free(r); + ESP_LOGE(TAG, "Malloc result name failed!"); + return; + } + memcpy(name_s, name, name_len); + name_s[name_len] = 0; + r->name = (const char *)name_s; + } + r->next = ble_scan_results; + ble_scan_results = r; + num_ble_scan_results++; +} +#endif /* CONFIG_BT_BLE_ENABLED */ + +#if !CONFIG_BT_NIMBLE_ENABLED void print_uuid(esp_bt_uuid_t *uuid) { if (uuid->len == ESP_UUID_LEN_16) { @@ -585,6 +645,7 @@ static void ble_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_p break; } } +#endif static esp_err_t init_ble_gap(void) { @@ -718,6 +779,252 @@ esp_err_t esp_hid_ble_gap_adv_start(void) * CONTROLLER INIT * */ +#if CONFIG_BT_NIMBLE_ENABLED +static esp_err_t init_low_level(uint8_t mode) +{ + esp_err_t ret; + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +#if CONFIG_IDF_TARGET_ESP32 + bt_cfg.mode = mode; +#endif + ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); + if (ret) { + ESP_LOGE(TAG, "esp_bt_controller_mem_release failed: %d", ret); + return ret; + } + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "esp_bt_controller_init failed: %d", ret); + return ret; + } + + ret = esp_bt_controller_enable(mode); + if (ret) { + ESP_LOGE(TAG, "esp_bt_controller_enable failed: %d", ret); + return ret; + } + + ret = esp_nimble_init(); + if (ret) { + ESP_LOGE(TAG, "esp_nimble_init failed: %d", ret); + return ret; + } + + return ret; +} + +static void handle_ble_device_result(const struct ble_gap_disc_desc *disc) +{ + int rc; + struct ble_hs_adv_fields fields; + uint16_t appearance; + uint8_t adv_name[BLE_HS_ADV_MAX_SZ]; + uint8_t adv_name_len = 0; + + appearance = 0; /* silent warnings for now */ + rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + if(rc != 0) { + return; + } + + if (fields.name != NULL) { + assert(fields.name_len < sizeof adv_name - 1); + memcpy(adv_name, fields.name, fields.name_len); + adv_name[fields.name_len] = '\0'; + adv_name_len = fields.name_len; + MODLOG_DFLT(DEBUG, " name(%scomplete)=%s\n", + fields.name_is_complete ? "" : "in", adv_name); + } + + if (fields.appearance_is_present) { + MODLOG_DFLT(DEBUG, " appearance=0x%04x\n", fields.appearance); + appearance = fields.appearance; + } + + for (int i = 0; i < fields.num_uuids16; i++) { + if (ble_uuid_u16(&fields.uuids16[i].u) == BLE_HID_SVC_UUID && + ((adv_name_len > 0 && memcmp("ESP BLE HID2", adv_name, adv_name_len) == 0) || + (adv_name_len > 0 && memcmp("ESP Mouse", adv_name, adv_name_len) == 0) || + (adv_name_len > 0 && memcmp("ESP Keyboard", adv_name, adv_name_len) == 0))) { + add_ble_scan_result(disc->addr.val, disc->addr.type, appearance, adv_name, adv_name_len, disc->rssi); + break; + } + } +} +#endif + +#if CONFIG_BT_NIMBLE_ENABLED +static int +nimble_hid_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_DISC: + handle_ble_device_result(&event->disc); + if (rc != 0) { + return 0; + } + + /* An advertisment report was received during GAP discovery. */ + return 0; + break; + case BLE_GAP_EVENT_DISC_COMPLETE: + MODLOG_DFLT(INFO, "discovery complete; reason=%d\n", + event->disc_complete.reason); + SEND_BLE_CB(); + return 0; + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + ESP_LOGI(TAG, "connection %s; status=%d", + event->connect.status == 0 ? "established" : "failed", + event->connect.status); + return 0; + break; + case BLE_GAP_EVENT_DISCONNECT: + ESP_LOGI(TAG, "disconnect; reason=%d", event->disconnect.reason); + + return 0; + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + ESP_LOGI(TAG, "connection updated; status=%d", + event->conn_update.status); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + ESP_LOGI(TAG, "advertise complete; reason=%d", + event->adv_complete.reason); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, + event->subscribe.attr_handle, + event->subscribe.reason, + event->subscribe.prev_notify, + event->subscribe.cur_notify, + event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + return 0; + + case BLE_GAP_EVENT_MTU: + ESP_LOGI(TAG, "mtu update event; conn_handle=%d cid=%d mtu=%d", + event->mtu.conn_handle, + event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + MODLOG_DFLT(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + return 0; + + case BLE_GAP_EVENT_NOTIFY_TX: + MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d " + "status=%d is_indication=%d", + event->notify_tx.conn_handle, + event->notify_tx.attr_handle, + event->notify_tx.status, + event->notify_tx.indication); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + ESP_LOGI(TAG, "PASSKEY_ACTION_EVENT started"); + struct ble_sm_io pkey = {0}; + int key = 0; + + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; // This is the passkey to be entered on peer + ESP_LOGI(TAG, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey); + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + ESP_LOGI(TAG, "Accepting passkey.."); + pkey.action = event->passkey.params.action; + pkey.numcmp_accept = key; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + static uint8_t tem_oob[16] = {0}; + pkey.action = event->passkey.params.action; + for (int i = 0; i < 16; i++) { + pkey.oob[i] = tem_oob[i]; + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + ESP_LOGI(TAG, "Input not supported passing -> 123456"); + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(TAG, "ble_sm_inject_io result: %d", rc); + } + return 0; + } + return 0; +} + +static esp_err_t start_nimble_scan(uint32_t seconds) +{ + uint8_t own_addr_type; + struct ble_gap_disc_params disc_params; + int rc; + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc); + return rc; + } + + /* Tell the controller to filter duplicates; we don't want to process + * repeated advertisements from the same device. + */ + disc_params.filter_duplicates = 1; + + /** + * Perform active scan. + */ + disc_params.passive = 0; + + /* Use defaults for the rest of the parameters. */ + disc_params.itvl = 0x50; + disc_params.window = 0x30; + disc_params.filter_policy = 0; + disc_params.limited = 0; + + rc = ble_gap_disc(own_addr_type, seconds * 1000, &disc_params, + nimble_hid_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "Error initiating GAP discovery procedure; rc=%d\n", + rc); + } + return rc; +} + +#else static esp_err_t init_low_level(uint8_t mode) { esp_err_t ret; @@ -779,6 +1086,7 @@ static esp_err_t init_low_level(uint8_t mode) #endif /* CONFIG_BT_BLE_ENABLED */ return ret; } +#endif esp_err_t esp_hid_gap_init(uint8_t mode) { @@ -833,6 +1141,14 @@ esp_err_t esp_hid_scan(uint32_t seconds, size_t *num_results, esp_hid_scan_resul return ESP_FAIL; } #endif /* CONFIG_BT_BLE_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED + if (start_nimble_scan(seconds) == ESP_OK) { + WAIT_BLE_CB(); + } else { + return ESP_FAIL; + } +#endif /* CONFIG_BT_BLE_ENABLED */ + #if CONFIG_BT_HID_HOST_ENABLED if (start_bt_scan(seconds) == ESP_OK) { diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h index de46b2fd31..ffd3dc9983 100644 --- a/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h +++ b/examples/bluetooth/esp_hid_host/main/esp_hid_gap.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -21,6 +21,8 @@ #endif #elif CONFIG_BT_BLE_ENABLED #define HID_HOST_MODE HIDH_BLE_MODE +#elif CONFIG_BT_NIMBLE_ENABLED +#define HID_HOST_MODE HIDH_BLE_MODE #else #define HID_HOST_MODE HIDH_IDLE_MODE #endif @@ -29,9 +31,11 @@ #include "esp_log.h" #include "esp_bt.h" +#if !CONFIG_BT_NIMBLE_ENABLED #include "esp_bt_defs.h" #include "esp_bt_main.h" #include "esp_gap_bt_api.h" +#endif #include "esp_hid_common.h" #if CONFIG_BT_BLE_ENABLED #include "esp_gattc_api.h" @@ -39,19 +43,28 @@ #include "esp_gap_ble_api.h" #endif +#if CONFIG_BT_NIMBLE_ENABLED +#include "nimble/ble.h" +#endif + #ifdef __cplusplus extern "C" { #endif typedef struct esp_hidh_scan_result_s { struct esp_hidh_scan_result_s *next; - +#if CONFIG_BT_NIMBLE_ENABLED + uint8_t bda[6]; +#else esp_bd_addr_t bda; +#endif + const char *name; int8_t rssi; esp_hid_usage_t usage; esp_hid_transport_t transport; //BT, BLE or USB union { + #if !CONFIG_BT_NIMBLE_ENABLED struct { esp_bt_cod_t cod; esp_bt_uuid_t uuid; @@ -60,6 +73,12 @@ typedef struct esp_hidh_scan_result_s { esp_ble_addr_type_t addr_type; uint16_t appearance; } ble; + #else + struct { + uint8_t addr_type; + uint16_t appearance; + } ble; + #endif }; } esp_hid_scan_result_t; @@ -70,8 +89,10 @@ void esp_hid_scan_results_free(esp_hid_scan_result_t *results); esp_err_t esp_hid_ble_gap_adv_init(uint16_t appearance, const char *device_name); esp_err_t esp_hid_ble_gap_adv_start(void); +#if !CONFIG_BT_NIMBLE_ENABLED void print_uuid(esp_bt_uuid_t *uuid); const char *ble_addr_type_str(esp_ble_addr_type_t ble_addr_type); +#endif #ifdef __cplusplus } diff --git a/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c b/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c index fe95e59c4f..f0535259fb 100644 --- a/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c +++ b/examples/bluetooth/esp_hid_host/main/esp_hid_host_main.c @@ -17,12 +17,34 @@ #include "esp_log.h" #include "nvs_flash.h" #include "esp_bt.h" + +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#else #include "esp_bt_defs.h" #include "esp_gap_ble_api.h" #include "esp_gatts_api.h" #include "esp_gatt_defs.h" #include "esp_bt_main.h" #include "esp_bt_device.h" +#endif + +#if CONFIG_BT_NIMBLE_ENABLED +#include "host/ble_hs.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#define ESP_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define ESP_BD_ADDR_HEX(addr) addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] +#else +#include "esp_bt_defs.h" +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#endif #include "esp_hidh.h" #include "esp_hid_gap.h" @@ -115,6 +137,13 @@ void hid_demo_task(void *pvParameters) printf("ADDR_TYPE: '%s', ", ble_addr_type_str(r->ble.addr_type)); } #endif /* CONFIG_BT_BLE_ENABLED */ +#if CONFIG_BT_NIMBLE_ENABLED + if (r->transport == ESP_HID_TRANSPORT_BLE) { + cr = r; + printf("APPEARANCE: 0x%04x, ", r->ble.appearance); + printf("ADDR_TYPE: '%d', ", r->ble.addr_type); + } +#endif /* CONFIG_BT_BLE_ENABLED */ #if CONFIG_BT_HID_HOST_ENABLED if (r->transport == ESP_HID_TRANSPORT_BT) { cr = r; @@ -149,6 +178,17 @@ void hid_demo_task(void *pvParameters) vTaskDelete(NULL); } +#if CONFIG_BT_NIMBLE_ENABLED +void ble_hid_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} +void ble_store_config_init(void); +#endif void app_main(void) { char bda_str[18] = {0}; @@ -176,5 +216,16 @@ void app_main(void) ESP_ERROR_CHECK( esp_hidh_init(&config) ); ESP_LOGI(TAG, "Own address:[%s]", bda2str((uint8_t *)esp_bt_dev_get_address(), bda_str, sizeof(bda_str))); +#if CONFIG_BT_NIMBLE_ENABLED + /* XXX Need to have template for store */ + ble_store_config_init(); + + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /* Starting nimble task after gatts is initialized*/ + ret = esp_nimble_enable(ble_hid_host_task); + if (ret) { + ESP_LOGE(TAG, "esp_nimble_enable failed: %d", ret); + } +#endif xTaskCreate(&hid_demo_task, "hid_task", 6 * 1024, NULL, 2, NULL); } diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 29000d27ab..6496cb0bbc 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -438,13 +438,9 @@ components/esp_hid/include/esp_hidd_transport.h components/esp_hid/include/esp_hidh.h components/esp_hid/include/esp_hidh_bluedroid.h components/esp_hid/include/esp_hidh_gattc.h -components/esp_hid/include/esp_hidh_transport.h -components/esp_hid/private/ble_hidd.h -components/esp_hid/private/ble_hidh.h components/esp_hid/private/bt_hidd.h components/esp_hid/private/bt_hidh.h components/esp_hid/private/esp_hidd_private.h -components/esp_hid/src/esp_hid_common.c components/esp_hid/test/hid_descriptor.h components/esp_hid/test/test_esp_hid.c components/esp_hw_support/include/esp_clk.h From 91578aafcfa2199c7dbb2e5a852ba3cd2f506d7c Mon Sep 17 00:00:00 2001 From: Roshan Bangar Date: Mon, 30 Oct 2023 14:34:55 +0530 Subject: [PATCH 07/21] feat(nimble): Gatt caching support --- components/bt/CMakeLists.txt | 3 ++ components/bt/host/nimble/Kconfig.in | 30 ++++++++++++++ .../host/nimble/port/include/esp_nimble_cfg.h | 10 +++++ components/bt/host/nimble/port/src/nvs_port.c | 40 +++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 components/bt/host/nimble/port/src/nvs_port.c diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index ad969579a6..f101b517f0 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -618,11 +618,14 @@ if(CONFIG_BT_ENABLED) "host/nimble/nimble/nimble/host/store/ram/src/ble_store_ram.c" "host/nimble/nimble/nimble/host/store/config/src/ble_store_config.c" "host/nimble/nimble/nimble/host/store/config/src/ble_store_nvs.c" + "host/nimble/nimble/nimble/host/src/ble_gattc_cache.c" + "host/nimble/nimble/nimble/host/src/ble_gattc_cache_conn.c" ) list(APPEND srcs "host/nimble/nimble/porting/nimble/src/nimble_port.c" "host/nimble/nimble/porting/npl/freertos/src/nimble_port_freertos.c" + "host/nimble/port/src/nvs_port.c" ) list(APPEND include_dirs host/nimble/nimble/porting/nimble/include diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 2a4365ac74..f154ae42e4 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -646,6 +646,36 @@ if BT_NIMBLE_50_FEATURE_SUPPORT Set this option to enable the Power Control feature endif +menuconfig BT_NIMBLE_GATT_CACHING + bool "Enable GATT caching" + depends on BT_NIMBLE_ENABLED && BT_NIMBLE_50_FEATURE_SUPPORT + help + Enable GATT caching +config BT_NIMBLE_GATT_CACHING_MAX_CONNS + int "Maximum connections to be cached" + depends on BT_NIMBLE_GATT_CACHING + default 1 + help + Set this option to set the upper limit on number of connections to be cached. +config BT_NIMBLE_GATT_CACHING_MAX_SVCS + int "Maximum number of services per connection" + depends on BT_NIMBLE_GATT_CACHING + default 64 + help + Set this option to set the upper limit on number of services per connection to be cached. +config BT_NIMBLE_GATT_CACHING_MAX_CHRS + int "Maximum number of characteristics per connection" + depends on BT_NIMBLE_GATT_CACHING + default 64 + help + Set this option to set the upper limit on number of characteristics per connection to be cached. +config BT_NIMBLE_GATT_CACHING_MAX_DSCS + int "Maximum number of descriptors per connection" + depends on BT_NIMBLE_GATT_CACHING + default 64 + help + Set this option to set the upper limit on number of discriptors per connection to be cached. + config BT_NIMBLE_WHITELIST_SIZE int "BLE white list size" depends on BT_NIMBLE_ENABLED diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 6098e142c4..8e09b0d63b 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -122,6 +122,16 @@ #define MYNEWT_VAL_BLE_MAX_PERIODIC_ADVERTISER_LIST (CONFIG_BT_NIMBLE_MAX_PERIODIC_ADVERTISER_LIST) #endif +#ifndef CONFIG_BT_NIMBLE_GATT_CACHING +#define MYNEWT_VAL_BLE_GATT_CACHING (0) +#else +#define MYNEWT_VAL_BLE_GATT_CACHING (CONFIG_BT_NIMBLE_GATT_CACHING) +#define MYNEWT_VAL_BLE_GATT_CACHING_MAX_CONNS (CONFIG_BT_NIMBLE_GATT_CACHING_MAX_CONNS) +#define MYNEWT_VAL_BLE_GATT_CACHING_MAX_SVCS (CONFIG_BT_NIMBLE_GATT_CACHING_MAX_SVCS) +#define MYNEWT_VAL_BLE_GATT_CACHING_MAX_CHRS (CONFIG_BT_NIMBLE_GATT_CACHING_MAX_CHRS) +#define MYNEWT_VAL_BLE_GATT_CACHING_MAX_DSCS (CONFIG_BT_NIMBLE_GATT_CACHING_MAX_DSCS) +#endif + #ifndef CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES #define MYNEWT_VAL_BLE_MULTI_ADV_INSTANCES (1) #else diff --git a/components/bt/host/nimble/port/src/nvs_port.c b/components/bt/host/nimble/port/src/nvs_port.c new file mode 100644 index 0000000000..05ffc3d694 --- /dev/null +++ b/components/bt/host/nimble/port/src/nvs_port.c @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _NVS_H +#define _NVS_H + +#include +#include "nvs.h" +#include "nimble/storage_port.h" + +static int +nvs_open_custom(const char* namespace_name, open_mode_t open_mode, cache_handle_t *out_handle) +{ + switch (open_mode) { + case READONLY: + return nvs_open(namespace_name, NVS_READONLY, out_handle); + + case READWRITE: + return nvs_open(namespace_name, NVS_READWRITE, out_handle); + + default: + return -1; + } +} + +struct cache_fn_mapping +link_storage_fn(void *storage_cb) +{ + struct cache_fn_mapping cache_fn; + cache_fn.open = nvs_open_custom; + cache_fn.close = nvs_close; + cache_fn.erase_all = nvs_erase_all; + cache_fn.write = nvs_set_blob; + cache_fn.read = nvs_get_blob; + return cache_fn; +} +#endif From 8997abe06442e65a003b692f6b523a09ef04559d Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Thu, 15 Feb 2024 11:39:36 +0530 Subject: [PATCH 08/21] feat(nimble): read multiple variable length characteristics --- components/bt/host/nimble/port/include/esp_nimble_cfg.h | 4 ++++ components/bt/porting/nimble/include/nimble/nimble_opt_auto.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 8e09b0d63b..8bbae57b44 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -603,6 +603,10 @@ #define MYNEWT_VAL_BLE_GATT_READ_MULT (MYNEWT_VAL_BLE_ROLE_CENTRAL) #endif +#ifndef MYNEWT_VAL_BLE_GATT_READ_MULT_VAR +#define MYNEWT_VAL_BLE_GATT_READ_MULT_VAR (MYNEWT_VAL_BLE_ROLE_CENTRAL) +#endif + #ifndef MYNEWT_VAL_BLE_GATT_READ_UUID #define MYNEWT_VAL_BLE_GATT_READ_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL) #endif diff --git a/components/bt/porting/nimble/include/nimble/nimble_opt_auto.h b/components/bt/porting/nimble/include/nimble/nimble_opt_auto.h index daf2153348..c4baec843b 100644 --- a/components/bt/porting/nimble/include/nimble/nimble_opt_auto.h +++ b/components/bt/porting/nimble/include/nimble/nimble_opt_auto.h @@ -84,6 +84,10 @@ extern "C" { #define NIMBLE_BLE_ATT_CLT_READ_MULT \ (MYNEWT_VAL(BLE_GATT_READ_MULT)) +#undef NIMBLE_BLE_ATT_CLT_READ_MULT_VAR +#define NIMBLE_BLE_ATT_CLT_READ_MULT_VAR \ + (MYNEWT_VAL(BLE_GATT_READ_MULT_VAR)) + #undef NIMBLE_BLE_ATT_CLT_READ_GROUP_TYPE #define NIMBLE_BLE_ATT_CLT_READ_GROUP_TYPE \ (MYNEWT_VAL(BLE_GATT_DISC_ALL_SVCS)) From 5d0009316a1fcd16882358b6b0fcab4ea388622e Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Fri, 29 Mar 2024 12:41:18 +0530 Subject: [PATCH 09/21] feat(nimble): Added support for EATT Credit Based Flow Control Mode --- components/bt/CMakeLists.txt | 1 + components/bt/host/nimble/Kconfig.in | 7 ++ .../host/nimble/port/include/esp_nimble_cfg.h | 71 ++++++++++++++++++- examples/bluetooth/nimble/blecent/main/main.c | 70 +++++++++++++++++- examples/bluetooth/nimble/bleprph/main/main.c | 38 +++++++++- 5 files changed, 184 insertions(+), 3 deletions(-) diff --git a/components/bt/CMakeLists.txt b/components/bt/CMakeLists.txt index f101b517f0..e82d002709 100644 --- a/components/bt/CMakeLists.txt +++ b/components/bt/CMakeLists.txt @@ -620,6 +620,7 @@ if(CONFIG_BT_ENABLED) "host/nimble/nimble/nimble/host/store/config/src/ble_store_nvs.c" "host/nimble/nimble/nimble/host/src/ble_gattc_cache.c" "host/nimble/nimble/nimble/host/src/ble_gattc_cache_conn.c" + "host/nimble/nimble/nimble/host/src/ble_eatt.c" ) list(APPEND srcs diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index f154ae42e4..3655ded6bd 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -937,3 +937,10 @@ menu "BLE Services" help Defines maximum number of report characteristics per service instance endmenu + +config BT_NIMBLE_EATT_CHAN_NUM + int "Maximum number of EATT channels" + default 0 + depends on BT_NIMBLE_ENABLED + help + Defines the number of channels EATT bearers can use diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 8bbae57b44..b2b81f5d0d 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -507,6 +507,10 @@ #define MYNEWT_VAL_BLE_ATT_SVR_NOTIFY (1) #endif +#ifndef MYNEWT_VAL_BLE_ATT_SVR_NOTIFY_MULTI +#define MYNEWT_VAL_BLE_ATT_SVR_NOTIFY_MULTI (1) +#endif + #ifndef MYNEWT_VAL_BLE_ATT_SVR_QUEUED_WRITE #define MYNEWT_VAL_BLE_ATT_SVR_QUEUED_WRITE (1) #endif @@ -531,6 +535,10 @@ #define MYNEWT_VAL_BLE_ATT_SVR_READ_MULT (1) #endif +#ifndef MYNEWT_VAL_BLE_ATT_SVR_READ_MULT_VAR +#define MYNEWT_VAL_BLE_ATT_SVR_READ_MULT_VAR (1) +#endif + #ifndef MYNEWT_VAL_BLE_ATT_SVR_READ_TYPE #define MYNEWT_VAL_BLE_ATT_SVR_READ_TYPE (1) #endif @@ -547,6 +555,49 @@ #define MYNEWT_VAL_BLE_ATT_SVR_WRITE_NO_RSP (1) #endif +#ifndef MYNEWT_VAL_BLE_EATT_CHAN_NUM +#define MYNEWT_VAL_BLE_EATT_CHAN_NUM (CONFIG_BT_NIMBLE_EATT_CHAN_NUM) +#endif + +#ifndef MYNEWT_VAL_BLE_EATT_LOG_LVL +#define MYNEWT_VAL_BLE_EATT_LOG_LVL (1) +#endif + +#ifndef MYNEWT_VAL_BLE_EATT_LOG_MOD +#define MYNEWT_VAL_BLE_EATT_LOG_MOD (27) +#endif + +#ifndef MYNEWT_VAL_BLE_EATT_MTU +#define MYNEWT_VAL_BLE_EATT_MTU (128) +#endif + +#ifndef MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES + +#if MYNEWT_VAL_BLE_GATT_CACHING +#define MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_ROBUST_CACHING (1) +#else +#define MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_ROBUST_CACHING (0) +#endif //MYNEWT_VAL_BLE_GATT_CACHING + +#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM +#define MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_EATT (2) +#else +#define MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_EATT (0) +#endif //CONFIG_BT_NIMBLE_EATT_CHAN_NUM + +#if MYNEWT_VAL_BLE_ATT_SVR_NOTIFY_MULTI +#define MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_NOTIFY_MULTI (4) +#else +#define MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_NOTIFY_MULTI (0) +#endif //MYNEWT_VAL_BLE_ATT_SVR_NOTIFY_MULTI + +#define MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES ( \ + MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_ROBUST_CACHING | \ + MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_EATT | \ + MYNEWT_VAL_BLE_CLIENT_SUPPORTED_FEATURES_NOTIFY_MULTI \ + ) +#endif //MYNEWT_VAL_CLIENT_SUPPORTED_FEATURES + #ifndef MYNEWT_VAL_BLE_GAP_MAX_PENDING_CONN_PARAM_UPDATE #define MYNEWT_VAL_BLE_GAP_MAX_PENDING_CONN_PARAM_UPDATE (1) #endif @@ -587,6 +638,10 @@ #define MYNEWT_VAL_BLE_GATT_NOTIFY (1) #endif +#ifndef MYNEWT_VAL_BLE_GATT_NOTIFY_MULTIPLE +#define MYNEWT_VAL_BLE_GATT_NOTIFY_MULTIPLE (1) +#endif + #ifndef MYNEWT_VAL_BLE_GATT_READ #define MYNEWT_VAL_BLE_GATT_READ (MYNEWT_VAL_BLE_ROLE_CENTRAL) #endif @@ -713,16 +768,26 @@ #define MYNEWT_VAL_BLE_HS_SYSINIT_STAGE (200) #endif +#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM +#define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (CONFIG_BT_NIMBLE_EATT_CHAN_NUM) +#else #ifndef CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM #define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (2) #else #define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM -#endif +#endif //CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM +#endif //CONFIG_BT_NIMBLE_EATT_CHAN_NUM #ifndef MYNEWT_VAL_BLE_L2CAP_COC_MPS #define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE-8) #endif +#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM +#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (1) +#else +#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0) +#endif + #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1) #endif @@ -736,8 +801,12 @@ #endif #ifndef MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS +#ifdef CONFIG_BT_NIMBLE_EATT_CHAN_NUM +#define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (CONFIG_BT_NIMBLE_EATT_CHAN_NUM) +#else #define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (1) #endif +#endif #ifndef MYNEWT_VAL_BLE_MESH #ifdef CONFIG_BT_NIMBLE_MESH diff --git a/examples/bluetooth/nimble/blecent/main/main.c b/examples/bluetooth/nimble/blecent/main/main.c index b2dc006940..41b6a6f6f9 100644 --- a/examples/bluetooth/nimble/blecent/main/main.c +++ b/examples/bluetooth/nimble/blecent/main/main.c @@ -62,6 +62,11 @@ static const char *tag = "NimBLE_BLE_CENT"; static int blecent_gap_event(struct ble_gap_event *event, void *arg); static uint8_t peer_addr[6]; +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 +static uint16_t cids[MYNEWT_VAL(BLE_EATT_CHAN_NUM)]; +static uint16_t bearers; +#endif + void ble_store_config_init(void); /** @@ -755,6 +760,14 @@ blecent_gap_event(struct ble_gap_event *event, void *arg) /* Forget about peer. */ peer_delete(event->disconnect.conn.conn_handle); +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 + /* Reset EATT config */ + bearers = 0; + for (int i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_NUM); i++) { + cids[i] = 0; + } +#endif + /* Resume scanning. */ blecent_scan(); return 0; @@ -771,14 +784,16 @@ blecent_gap_event(struct ble_gap_event *event, void *arg) rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); assert(rc == 0); print_conn_desc(&desc); +#if !MYNEWT_VAL(BLE_EATT_CHAN_NUM) #if CONFIG_EXAMPLE_ENCRYPTION /*** Go for service discovery after encryption has been successfully enabled ***/ - rc = peer_disc_all(event->connect.conn_handle, + rc = peer_disc_all(event->enc_change.conn_handle, blecent_on_disc_complete, NULL); if (rc != 0) { MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc); return 0; } +#endif #endif return 0; @@ -851,6 +866,51 @@ blecent_gap_event(struct ble_gap_event *event, void *arg) return 0; #endif +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 + case BLE_GAP_EVENT_EATT: + int i; + MODLOG_DFLT(INFO, "EATT %s : conn_handle=%d cid=%d", + event->eatt.status ? "disconnected" : "connected", + event->eatt.conn_handle, + event->eatt.cid); + if (event->eatt.status) { + /* Remove CID from the list of saved CIDs */ + for (i = 0; i < bearers; i++) { + if (cids[i] == event->eatt.cid) { + break; + } + } + while (i < (bearers - 1)) { + cids[i] = cids[i + 1]; + i += 1; + } + cids[i] = 0; + + /* Now Abort */ + return 0; + } + cids[bearers] = event->eatt.cid; + bearers += 1; + if (bearers != MYNEWT_VAL(BLE_EATT_CHAN_NUM)) { + /* Wait until all EATT bearers are connected before proceeding */ + return 0; + } + /* Set the default bearer to use for further procedures */ + rc = ble_att_set_default_bearer_using_cid(event->eatt.conn_handle, cids[0]); + if (rc != 0) { + MODLOG_DFLT(INFO, "Cannot set default EATT bearer, rc = %d\n", rc); + return rc; + } + + /* Perform service discovery */ + rc = peer_disc_all(event->eatt.conn_handle, + blecent_on_disc_complete, NULL); + if(rc != 0) { + MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc); + return 0; + } +#endif /* MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 */ + return 0; default: return 0; } @@ -871,6 +931,7 @@ blecent_on_sync(void) rc = ble_hs_util_ensure_addr(0); assert(rc == 0); + #if !CONFIG_EXAMPLE_INIT_DEINIT_LOOP /* Begin scanning for a peripheral to connect to. */ blecent_scan(); @@ -962,4 +1023,11 @@ app_main(void) stack_init_deinit(); #endif +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 + bearers = 0; + for (int i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_NUM); i++) { + cids[i] = 0; + } +#endif + } diff --git a/examples/bluetooth/nimble/bleprph/main/main.c b/examples/bluetooth/nimble/bleprph/main/main.c index b1a5800a14..6a1526387a 100644 --- a/examples/bluetooth/nimble/bleprph/main/main.c +++ b/examples/bluetooth/nimble/bleprph/main/main.c @@ -45,6 +45,11 @@ static uint8_t own_addr_type = BLE_OWN_ADDR_RANDOM; static uint8_t own_addr_type; #endif +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 +static uint16_t cids[MYNEWT_VAL(BLE_EATT_CHAN_NUM)]; +static uint16_t bearers; +#endif + void ble_store_config_init(void); /** @@ -418,13 +423,37 @@ bleprph_gap_event(struct ble_gap_event *event, void *arg) event->transmit_power.delta); return 0; - case BLE_GAP_EVENT_PATHLOSS_THRESHOLD: + case BLE_GAP_EVENT_PATHLOSS_THRESHOLD: MODLOG_DFLT(INFO, "Pathloss threshold event : conn_handle=%d current path loss=%d " "zone_entered =%d", event->pathloss_threshold.conn_handle, event->pathloss_threshold.current_path_loss, event->pathloss_threshold.zone_entered); return 0; +#endif +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 + case BLE_GAP_EVENT_EATT: + MODLOG_DFLT(INFO, "EATT %s : conn_handle=%d cid=%d", + event->eatt.status ? "disconnected" : "connected", + event->eatt.conn_handle, + event->eatt.cid); + if (event->eatt.status) { + /* Abort if disconnected */ + return 0; + } + cids[bearers] = event->eatt.cid; + bearers += 1; + if (bearers != MYNEWT_VAL(BLE_EATT_CHAN_NUM)) { + /* Wait until all EATT bearers are connected before proceeding */ + return 0; + } + /* Set the default bearer to use for further procedures */ + rc = ble_att_set_default_bearer_using_cid(event->eatt.conn_handle, cids[0]); + if (rc != 0) { + MODLOG_DFLT(INFO, "Cannot set default EATT bearer, rc = %d\n", rc); + return rc; + } + return 0; #endif } return 0; @@ -567,4 +596,11 @@ app_main(void) if (rc != ESP_OK) { ESP_LOGE(tag, "scli_init() failed"); } + +#if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 + bearers = 0; + for (int i = 0; i < MYNEWT_VAL(BLE_EATT_CHAN_NUM); i++) { + cids[i] = 0; + } +#endif } From c9337d120b8f058bf956964aab36a223451afc51 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Mon, 24 Jun 2024 19:20:51 +0530 Subject: [PATCH 10/21] feat(nimble): Added LE GATT Security Levels Characteristic --- components/bt/host/nimble/Kconfig.in | 6 ++++++ components/bt/host/nimble/port/include/esp_nimble_cfg.h | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 3655ded6bd..a3cfcb00b2 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -913,6 +913,12 @@ menu "GAP Service" Peripheral Preferred Connection Parameter: Supervision Timeout Timeout = Value * 10 ms + config BT_NIMBLE_SVC_GAP_GATT_SECURITY_LEVEL + bool "LE GATT Security Level Characteristic" + default n + help + Enable the LE GATT Security Level Characteristic + endmenu menu "BLE Services" diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index b2b81f5d0d..4be642c3da 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -1674,6 +1674,11 @@ CONFIG_BT_NIMBLE_SVC_GAP_PPCP_SUPERVISION_TMO #endif +#ifndef MYNEWT_VAL_BLE_SVC_GAP_GATT_SECURITY_LEVEL +#define MYNEWT_VAL_BLE_SVC_GAP_GATT_SECURITY_LEVEL \ + CONFIG_BT_NIMBLE_SVC_GAP_GATT_SECURITY_LEVEL +#endif + /*** nimble/transport */ #ifndef MYNEWT_VAL_BLE_HCI_TRANSPORT_EMSPI #define MYNEWT_VAL_BLE_HCI_TRANSPORT_EMSPI (0) From c240d55f6cf83608983d6e56171d282cefad333d Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Tue, 2 Jul 2024 17:59:10 +0530 Subject: [PATCH 11/21] fix(nimble): Fixed encrypted advertisement data example --- components/bt/host/nimble/Kconfig.in | 1 - .../enc_adv_data_cent/main/main.c | 125 ++++++++----- .../enc_adv_data_cent/sdkconfig.defaults | 2 +- .../enc_adv_data_prph/main/Kconfig.projbuild | 2 +- .../enc_adv_data_prph/main/main.c | 171 ++++++++++-------- .../enc_adv_data_prph/sdkconfig.defaults | 2 +- 6 files changed, 178 insertions(+), 125 deletions(-) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index a3cfcb00b2..82ad325a55 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -740,7 +740,6 @@ config BT_NIMBLE_VS_SUPPORT config BT_NIMBLE_ENC_ADV_DATA bool "Encrypted Advertising Data" depends on BT_NIMBLE_50_FEATURE_SUPPORT - select BT_NIMBLE_EXT_ADV help This option is used to enable encrypted advertising data. diff --git a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c index 127c412312..d0a4a86bf0 100644 --- a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c +++ b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/main/main.c @@ -261,8 +261,11 @@ enc_adv_data_cent_decrypt(uint8_t length_data, const uint8_t *data, const uint8_ dec_data_len = temp[0]; - MODLOG_DFLT(DEBUG, "Data after decryption:"); - print_bytes(temp, dec_data_len); + MODLOG_DFLT(INFO, "Data after decryption:"); + for (int i = 0; i < dec_data_len + 1; i++) { + MODLOG_DFLT(INFO, "0x%02X ", temp[i]); + } + MODLOG_DFLT(INFO, "\n"); return 1; default: @@ -279,13 +282,19 @@ enc_adv_data_cent_decrypt(uint8_t length_data, const uint8_t *data, const uint8_ * advertises connectability and support for the Key Characteristic service. */ static int -ext_enc_adv_data_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) +enc_adv_data_cent_should_connect(const struct ble_gap_disc_desc *disc) { - int offset = 0; - int ad_struct_len = 0; + struct ble_hs_adv_fields fields; + int rc; + int i; - if (disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && - disc->legacy_event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { + if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND && + disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) { + return 0; + } + + rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + if (rc != 0) { return 0; } @@ -302,41 +311,31 @@ ext_enc_adv_data_cent_should_connect(const struct ble_gap_ext_disc_desc *disc) /* The device has to advertise support for the Key Characteristic * service (0x2B88) + * + * Check if custom UUID 0x2C01 is advertised */ - do { - ad_struct_len = disc->data[offset]; + for (i = 0; i < fields.num_uuids16; i++) { + if (ble_uuid_u16(&fields.uuids16[i].u) == 0x2C01) { + if (enc_adv_data_find_peer(disc->addr.val) != -1) { + MODLOG_DFLT(INFO, "Peer was already added with addr : %s", + addr_str(&disc->addr.val)); + } else { + MODLOG_DFLT(INFO, "Adding peer addr : %s", addr_str(&disc->addr.val)); - if (!ad_struct_len) { - break; - } + memcpy(&kmp[counter].peer_addr, &disc->addr.val, PEER_ADDR_VAL_SIZE); + counter++; - /* Search if custom service UUID (0x2C01) is advertised */ - if (disc->data[offset] == 0x03 && disc->data[offset + 1] == 0x03) { - if ( disc->data[offset + 2] == 0x2C && disc->data[offset + 3] == 0x01 ) { - if (enc_adv_data_find_peer(disc->addr.val) != -1) { - MODLOG_DFLT(INFO, "Peer was already added with addr : %s", - addr_str(&disc->addr.val)); - } else { - MODLOG_DFLT(INFO, "Adding peer addr : %s", addr_str(&disc->addr.val)); - - memcpy(&kmp[counter].peer_addr, &disc->addr.val, PEER_ADDR_VAL_SIZE); - counter++; - - if (counter > CONFIG_BT_NIMBLE_MAX_CONNECTIONS) { - counter = 0; - } - } - if (enc_adv_data_check_km_exist(disc->addr.val)) { - return enc_adv_data_cent_decrypt(disc->length_data, disc->data, disc->addr.val); - } else { - return 1; + if (counter > CONFIG_BT_NIMBLE_MAX_CONNECTIONS) { + counter = 0; } } + if (enc_adv_data_check_km_exist(disc->addr.val)) { + return enc_adv_data_cent_decrypt(disc->length_data, disc->data, disc->addr.val); + } else { + return 1; + } } - - offset += ad_struct_len + 1; - - } while ( offset < disc->length_data ); + } return 0; } @@ -354,7 +353,7 @@ enc_adv_data_cent_connect_if_interesting(void *disc) ble_addr_t *addr; /* Don't do anything if we don't care about this advertiser. */ - if (!ext_enc_adv_data_cent_should_connect((struct ble_gap_ext_disc_desc *)disc)) { + if (!enc_adv_data_cent_should_connect((struct ble_gap_disc_desc *)disc)) { return; } @@ -377,7 +376,7 @@ enc_adv_data_cent_connect_if_interesting(void *disc) /* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for * timeout. */ - addr = &((struct ble_gap_ext_disc_desc *)disc)->addr; + addr = &((struct ble_gap_disc_desc *)disc)->addr; rc = ble_gap_connect(own_addr_type, addr, 30000, NULL, enc_adv_data_cent_gap_event, NULL); @@ -459,13 +458,16 @@ enc_adv_data_cent_gap_event(struct ble_gap_event *event, void *arg) return 0; } - /* Perform service discovery */ - rc = peer_disc_all(event->connect.conn_handle, - enc_adv_data_cent_on_disc_complete, NULL); + /** Authorization is required for this characterisitc */ + rc = ble_gap_security_initiate(event->connect.conn_handle); if (rc != 0) { - MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc); - return 0; + MODLOG_DFLT(INFO, "Security could not be initiated, rc = %d\n", rc); + return ble_gap_terminate(event->connect.conn_handle, + BLE_ERR_REM_USER_CONN_TERM); + } else { + MODLOG_DFLT(INFO, "Connection secured\n"); } + } else { /* Connection attempt failed; resume scanning. */ MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n", @@ -493,6 +495,21 @@ enc_adv_data_cent_gap_event(struct ble_gap_event *event, void *arg) event->disc_complete.reason); return 0; + case BLE_GAP_EVENT_ENC_CHANGE: + MODLOG_DFLT(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + + /* Perform service discovery */ + rc = peer_disc_all(event->enc_change.conn_handle, + enc_adv_data_cent_on_disc_complete, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc); + } + return 0; + case BLE_GAP_EVENT_NOTIFY_RX: /* Peer sent us a notification or indication. */ MODLOG_DFLT(INFO, "received %s; conn_handle=%d attr_handle=%d " @@ -515,11 +532,25 @@ enc_adv_data_cent_gap_event(struct ble_gap_event *event, void *arg) event->mtu.value); return 0; +#if MYNEWT_VAL(BLE_EXT_ADV) case BLE_GAP_EVENT_EXT_DISC: /* An advertisement report was received during GAP discovery. */ ext_print_adv_report(&event->disc); + return 0; +#endif + + case BLE_GAP_EVENT_PASSKEY_ACTION: + ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started %d", event->passkey.params.action); + struct ble_sm_io pkey = {0}; + + if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; + ESP_LOGI(tag, "Entering passkey %" PRIu32, pkey.passkey); + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc); + } - enc_adv_data_cent_connect_if_interesting(&event->disc); return 0; default: @@ -577,12 +608,16 @@ app_main(void) ble_hs_cfg.sync_cb = enc_adv_data_cent_on_sync; ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /** This feature requires authentication */ + ble_hs_cfg.sm_mitm = 1; + ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_KEYBOARD_ONLY; + /* Initialize data structures to track connected peers. */ rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64); assert(rc == 0); /* Set the default device name. */ - rc = ble_svc_gap_device_name_set("nimble-enc_adv_data_cent"); + rc = ble_svc_gap_device_name_set("enc_adv_data_cent"); assert(rc == 0); /* XXX Need to have template for store */ diff --git a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults index 3ff7b7d389..31b1e163f0 100644 --- a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults +++ b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_cent/sdkconfig.defaults @@ -10,5 +10,5 @@ CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n CONFIG_BTDM_CTRL_MODE_BTDM=n CONFIG_BT_BLUEDROID_ENABLED=n CONFIG_BT_NIMBLE_ENABLED=y -CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_EXT_ADV=n CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=y diff --git a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/Kconfig.projbuild b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/Kconfig.projbuild index be90b080c5..c2c6c4955b 100644 --- a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/Kconfig.projbuild +++ b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/Kconfig.projbuild @@ -2,7 +2,7 @@ menu "Example Configuration" choice EXAMPLE_USE_IO_TYPE prompt "I/O Capability" - default BLE_SM_IO_CAP_NO_IO + default BLE_SM_IO_CAP_DISP_ONLY help I/O capability of device. diff --git a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/main.c b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/main.c index 8593f3d441..ff2536c3a2 100644 --- a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/main.c +++ b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/main.c @@ -16,27 +16,21 @@ #include "enc_adv_data_prph.h" #if CONFIG_EXAMPLE_ENC_ADV_DATA -static uint8_t km_adv_pattern_1[] = { - 0x02, 0x01, 0x06, - 0x03, 0x03, 0x2C, 0x01, - 0x04, 0X09, 'k', 'e', 'y', -}; static const char *tag = "ENC_ADV_DATA_PRPH"; static int enc_adv_data_prph_gap_event(struct ble_gap_event *event, void *arg); +const uint8_t device_name[3] = {'k', 'e', 'y'}; -static uint8_t ext_adv_pattern_1[] = { - 0x02, 0x01, 0x06, - 0x03, 0x03, 0x2C, 0x00, - 0x05, 0X09, 'p', 'r', 'p', 'h', +static uint8_t unencrypted_adv_pattern[] = { + 0x05, 0X09, 'p', 'r', 'p', 'h' }; struct key_material km = { .session_key = { - 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, - 0xCC, 0xCD, 0xCE, 0xCF + 0x19, 0x6a, 0x0a, 0xd1, 0x2a, 0x61, 0x20, 0x1e, + 0x13, 0x6e, 0x2e, 0xd1, 0x12, 0xda, 0xa9, 0x57 }, - .iv = {0xFB, 0x56, 0xE1, 0xDA, 0xDC, 0x7E, 0xAD, 0xF5}, + .iv = {0x9E, 0x7a, 0x00, 0xef, 0xb1, 0x7a, 0xe7, 0x46}, }; #if CONFIG_EXAMPLE_RANDOM_ADDR @@ -74,31 +68,24 @@ enc_adv_data_prph_print_conn_desc(struct ble_gap_conn_desc *desc) desc->sec_state.bonded); } -static const struct enc_adv_data ead[] = { - ENC_ADV_DATA(BLE_GAP_ENC_ADV_DATA, ext_adv_pattern_1, sizeof(ext_adv_pattern_1)), -}; - -static void enc_adv_data_prph_encrypt_set(uint8_t instance, struct os_mbuf *data) +static void +enc_adv_data_prph_encrypt_set(uint8_t * out_encrypted_adv_data, + const unsigned encrypted_adv_data_len) { int rc; - uint8_t enc_data_flag = BLE_GAP_ENC_ADV_DATA; //0x31 + const unsigned unencrypted_adv_data_len = sizeof(unencrypted_adv_pattern); - uint8_t ext_adv_pattern_sz = ead[0].len; + uint8_t unencrypted_adv_data[unencrypted_adv_data_len]; + uint8_t encrypted_adv_data[encrypted_adv_data_len]; - size_t adv_data_sz = BLE_GAP_DATA_SERIALIZED_SIZE(ext_adv_pattern_sz); - uint8_t adv_data[adv_data_sz]; + memcpy(unencrypted_adv_data, unencrypted_adv_pattern, sizeof(unencrypted_adv_pattern)); - size_t enc_adv_data_sz = BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(adv_data_sz); - uint8_t enc_adv_data[enc_adv_data_sz]; + MODLOG_DFLT(INFO, "Data before encryption:"); + print_bytes(unencrypted_adv_data, unencrypted_adv_data_len); + MODLOG_DFLT(INFO, "\n"); - ble_ead_serialize_data(&ead[0], adv_data); - - MODLOG_DFLT(DEBUG, "Data before encryption:"); - print_bytes(adv_data, adv_data_sz); - MODLOG_DFLT(DEBUG, "\n"); - - rc = ble_ead_encrypt(km.session_key, km.iv, adv_data, adv_data_sz, enc_adv_data); + rc = ble_ead_encrypt(km.session_key, km.iv, unencrypted_adv_data, unencrypted_adv_data_len, encrypted_adv_data); if (rc == 0) { MODLOG_DFLT(INFO, "Encryption of adv data done successfully"); } else { @@ -106,20 +93,12 @@ static void enc_adv_data_prph_encrypt_set(uint8_t instance, struct os_mbuf *data return; } - MODLOG_DFLT(DEBUG, "Data after encryption:"); - print_bytes(enc_adv_data, enc_adv_data_sz); - MODLOG_DFLT(DEBUG, "\n"); + MODLOG_DFLT(INFO, "Data after encryption:"); + print_bytes(encrypted_adv_data, encrypted_adv_data_len); + MODLOG_DFLT(INFO, "\n"); - //Copying encrypted data - rc = os_mbuf_append(data, &enc_adv_data_sz, sizeof(uint8_t)); - - rc = os_mbuf_append(data, &enc_data_flag, sizeof(uint8_t)); - - rc = os_mbuf_append(data, enc_adv_data, enc_adv_data_sz); - assert(rc == 0); - - MODLOG_DFLT(INFO, "Advertising data:"); - print_mbuf(data); + /** Contains Randomiser ## Encrypted Advertising Data ## MIC */ + memcpy(out_encrypted_adv_data, encrypted_adv_data, encrypted_adv_data_len); } /** @@ -128,57 +107,59 @@ static void enc_adv_data_prph_encrypt_set(uint8_t instance, struct os_mbuf *data * o Undirected connectable mode. */ static void -ext_enc_adv_data_prph_advertise(void) +enc_adv_data_prph_advertise(void) { - struct ble_gap_ext_adv_params params; - uint8_t instance = 0; + struct ble_gap_adv_params params; + struct ble_hs_adv_fields fields; + uint8_t own_addr_type; int rc; - struct os_mbuf *data; + const unsigned encrypted_adv_data_len = BLE_EAD_ENCRYPTED_PAYLOAD_SIZE(sizeof(unencrypted_adv_pattern)); + uint8_t encrypted_adv_data[encrypted_adv_data_len]; + memset(encrypted_adv_data, 0, encrypted_adv_data_len); /* First check if any instance is already active */ - if (ble_gap_ext_adv_active(instance)) { + if (ble_gap_adv_active()) { return; } /* use defaults for non-set params */ memset (¶ms, 0, sizeof(params)); + memset (&fields, 0, sizeof(fields)); + + own_addr_type = BLE_OWN_ADDR_PUBLIC; /* enable connectable advertising */ - params.connectable = 1; - - /* advertise using random addr */ - params.own_addr_type = BLE_OWN_ADDR_PUBLIC; - - params.primary_phy = BLE_HCI_LE_PHY_1M; - params.secondary_phy = BLE_HCI_LE_PHY_2M; - //params.tx_power = 127; - params.sid = 1; + params.conn_mode = BLE_GAP_CONN_MODE_UND; + params.disc_mode = BLE_GAP_DISC_MODE_GEN; params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN; params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN; - /* configure instance 0 */ - rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, - enc_adv_data_prph_gap_event, NULL); - assert (rc == 0); + fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; - /* in this case only scan response is allowed */ - /* get mbuf for scan rsp data */ - data = os_msys_get_pkthdr(sizeof(km_adv_pattern_1), 0); - assert(data); + fields.name = device_name; + fields.name_len = 3; + fields.name_is_complete = 1; - rc = os_mbuf_append(data, km_adv_pattern_1, sizeof(km_adv_pattern_1)); - assert(rc == 0); + fields.uuids16 = (ble_uuid16_t[]) { + BLE_UUID16_INIT(0x2C01) /** For the central to recognise this device */ + }; + fields.num_uuids16 = 1; + fields.uuids16_is_complete = 1; - //Encrypted advertising data - enc_adv_data_prph_encrypt_set(instance, data); + /** Getting the encrypted advertising data */ + enc_adv_data_prph_encrypt_set(encrypted_adv_data, encrypted_adv_data_len); - rc = ble_gap_ext_adv_set_data(instance, data); + fields.enc_adv_data = encrypted_adv_data; + fields.enc_adv_data_len = encrypted_adv_data_len; + + rc = ble_gap_adv_set_fields(&fields); assert (rc == 0); /* start advertising */ - rc = ble_gap_ext_adv_start(instance, 0, 0); + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, + ¶ms, enc_adv_data_prph_gap_event, NULL); assert (rc == 0); } @@ -218,7 +199,7 @@ enc_adv_data_prph_gap_event(struct ble_gap_event *event, void *arg) if (event->connect.status != 0) { /* Connection failed; resume advertising. */ - ext_enc_adv_data_prph_advertise(); + enc_adv_data_prph_advertise(); } return 0; @@ -229,7 +210,7 @@ enc_adv_data_prph_gap_event(struct ble_gap_event *event, void *arg) MODLOG_DFLT(INFO, "\n"); /* Connection terminated; resume advertising. */ - ext_enc_adv_data_prph_advertise(); + enc_adv_data_prph_advertise(); return 0; case BLE_GAP_EVENT_CONN_UPDATE: @@ -247,6 +228,30 @@ enc_adv_data_prph_gap_event(struct ble_gap_event *event, void *arg) event->adv_complete.reason); return 0; + case BLE_GAP_EVENT_ENC_CHANGE: + MODLOG_DFLT(INFO, "encryption change event; status=%d ", + event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + enc_adv_data_prph_print_conn_desc(&desc); + MODLOG_DFLT(INFO, "\n"); + return 0; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started"); + struct ble_sm_io pkey = {0}; + + /** For now only BLE_SM_IOACT_DISP is handled */ + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; + ESP_LOGI(tag, "Enter passkey %" PRIu32 " on the peer side", pkey.passkey); + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc); + } + + return 0; + case BLE_GAP_EVENT_NOTIFY_TX: MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d " "status=%d is_indication=%d", @@ -274,6 +279,15 @@ enc_adv_data_prph_gap_event(struct ble_gap_event *event, void *arg) event->mtu.channel_id, event->mtu.value); return 0; + + case BLE_GAP_EVENT_AUTHORIZE: + MODLOG_DFLT(INFO, "authorization event; conn_handle=%d attr_handle=%d is_read=%d", + event->authorize.conn_handle, + event->authorize.attr_handle, + event->authorize.is_read); + /** Accept all authorization requests for now */ + event->authorize.out_response = BLE_GAP_AUTHORIZE_ACCEPT; + return 0; } return 0; @@ -337,7 +351,7 @@ enc_adv_data_prph_on_sync(void) MODLOG_DFLT(INFO, "\n"); /* Begin advertising. */ - ext_enc_adv_data_prph_advertise(); + enc_adv_data_prph_advertise(); } void enc_adv_data_prph_host_task(void *param) @@ -373,17 +387,21 @@ app_main(void) ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; ble_hs_cfg.store_status_cb = ble_store_util_status_rr; - ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE; -#ifdef CONFIG_EXAMPLE_BONDING +#if CONFIG_EXAMPLE_BONDING ble_hs_cfg.sm_bonding = 1; /* Enable the appropriate bit masks to make sure the keys * that are needed are exchanged */ ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC; ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC; +#else + ble_hs_cfg.sm_bonding = 0; #endif + /** This feature requires authentication */ ble_hs_cfg.sm_mitm = 1; + ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE; + #ifdef CONFIG_EXAMPLE_USE_SC ble_hs_cfg.sm_sc = 1; #else @@ -403,6 +421,7 @@ app_main(void) assert(rc == 0); /* Set the session key and initialization vector */ + rc = ble_svc_gap_device_key_material_set(km.session_key, km.iv); assert(rc == 0); diff --git a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults index 3f4f3bfba3..6f5148de6f 100644 --- a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults +++ b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/sdkconfig.defaults @@ -11,4 +11,4 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n CONFIG_BT_BLUEDROID_ENABLED=n CONFIG_BT_NIMBLE_ENABLED=y CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70 -CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_EXT_ADV=n From b7ace3d6c66817d1b6f1260496fef9132299ae79 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Fri, 12 Jul 2024 17:16:37 +0530 Subject: [PATCH 12/21] fix(nimble): Fixed Device Information and Scan Parameters service --- components/bt/host/nimble/Kconfig.in | 65 +++++++++++++++++++ .../host/nimble/port/include/esp_nimble_cfg.h | 51 ++++++++++----- 2 files changed, 99 insertions(+), 17 deletions(-) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 82ad325a55..1f89d82728 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -941,6 +941,71 @@ menu "BLE Services" default 3 help Defines maximum number of report characteristics per service instance + + config BT_NIMBLE_SVC_BAS_BATTERY_LEVEL_NOTIFY + depends on BT_NIMBLE_ENABLED + bool "BAS Battery Level NOTIFY permission" + default n + help + Enable/Disable notifications on BAS Battery Level Characteristic + + menu "Device Information Service" + config BT_NIMBLE_SVC_DIS_MANUFACTURER_NAME + depends on BT_NIMBLE_ENABLED + bool "Manufacturer Name" + default n + help + Enable the DIS characteristic Manufacturer Name String characteristic + + config BT_NIMBLE_SVC_DIS_SERIAL_NUMBER + depends on BT_NIMBLE_ENABLED + bool "Serial Number" + default n + help + Enable the DIS Serial Number characteristic + + config BT_NIMBLE_SVC_DIS_HARDWARE_REVISION + depends on BT_NIMBLE_ENABLED + bool "Hardware Revision" + default n + help + Enable the DIS Hardware Revision characteristic + + config BT_NIMBLE_SVC_DIS_FIRMWARE_REVISION + depends on BT_NIMBLE_ENABLED + bool "Firmware Revision" + default n + help + Enable the DIS Firmware Revision characteristic + + config BT_NIMBLE_SVC_DIS_SOFTWARE_REVISION + depends on BT_NIMBLE_ENABLED + bool "Software Revision" + default n + help + Enable the DIS Software Revision characteristic + + config BT_NIMBLE_SVC_DIS_SYSTEM_ID + depends on BT_NIMBLE_ENABLED + bool "System ID" + default n + help + Enable the DIS System ID characteristic + + config BT_NIMBLE_SVC_DIS_PNP_ID + depends on BT_NIMBLE_ENABLED + bool "PnP ID" + default n + help + Enable the DIS PnP ID characteristic + + config BT_NIMBLE_SVC_DIS_INCLUDED + depends on BT_NIMBLE_ENABLED + bool "DIS as an Included Service" + default n + help + Use DIS as an included service + endmenu endmenu config BT_NIMBLE_EATT_CHAN_NUM diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 4be642c3da..d1ff729add 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -960,7 +960,7 @@ /*** nimble/host/services/bas */ #ifndef MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_NOTIFY_ENABLE -#define MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_NOTIFY_ENABLE (1) +#define MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_NOTIFY_ENABLE (CONFIG_BT_NIMBLE_SVC_BAS_BATTERY_LEVEL_NOTIFY) #endif #ifndef MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_READ_PERM @@ -1521,7 +1521,7 @@ /*** @apache-mynewt-nimble/nimble/host/services/bas */ #ifndef MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_NOTIFY_ENABLE -#define MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_NOTIFY_ENABLE (1) +#define MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_NOTIFY_ENABLE (CONFIG_BT_NIMBLE_SVC_BAS_BATTERY_LEVEL_NOTIFY) #endif #ifndef MYNEWT_VAL_BLE_SVC_BAS_BATTERY_LEVEL_READ_PERM @@ -1538,29 +1538,34 @@ #endif #ifndef MYNEWT_VAL_BLE_SVC_DIS_FIRMWARE_REVISION_DEFAULT -#define MYNEWT_VAL_BLE_SVC_DIS_FIRMWARE_REVISION_DEFAULT (NULL) +#define MYNEWT_VAL_BLE_SVC_DIS_FIRMWARE_REVISION_DEFAULT ("0000") #endif /* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ -#ifndef MYNEWT_VAL_BLE_SVC_DIS_FIRMWARE_REVISION_READ_PERM +#if CONFIG_BT_NIMBLE_SVC_DIS_FIRMWARE_REVISION +#define MYNEWT_VAL_BLE_SVC_DIS_FIRMWARE_REVISION_READ_PERM (0) +#else #define MYNEWT_VAL_BLE_SVC_DIS_FIRMWARE_REVISION_READ_PERM (-1) #endif #ifndef MYNEWT_VAL_BLE_SVC_DIS_HARDWARE_REVISION_DEFAULT -#define MYNEWT_VAL_BLE_SVC_DIS_HARDWARE_REVISION_DEFAULT (NULL) +#define MYNEWT_VAL_BLE_SVC_DIS_HARDWARE_REVISION_DEFAULT ("0000") #endif /* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ -#ifndef MYNEWT_VAL_BLE_SVC_DIS_HARDWARE_REVISION_READ_PERM +#if CONFIG_BT_NIMBLE_SVC_DIS_HARDWARE_REVISION +#define MYNEWT_VAL_BLE_SVC_DIS_HARDWARE_REVISION_READ_PERM (0) +#else #define MYNEWT_VAL_BLE_SVC_DIS_HARDWARE_REVISION_READ_PERM (-1) #endif #ifndef MYNEWT_VAL_BLE_SVC_DIS_MANUFACTURER_NAME_DEFAULT -#define MYNEWT_VAL_BLE_SVC_DIS_MANUFACTURER_NAME_DEFAULT (NULL) +#define MYNEWT_VAL_BLE_SVC_DIS_MANUFACTURER_NAME_DEFAULT ("espressif") #endif -/* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ -#ifndef MYNEWT_VAL_BLE_SVC_DIS_MANUFACTURER_NAME_READ_PERM +#if CONFIG_BT_NIMBLE_SVC_DIS_MANUFACTURER_NAME +#define MYNEWT_VAL_BLE_SVC_DIS_MANUFACTURER_NAME_READ_PERM (0) +#else #define MYNEWT_VAL_BLE_SVC_DIS_MANUFACTURER_NAME_READ_PERM (-1) #endif @@ -1573,20 +1578,24 @@ #endif #ifndef MYNEWT_VAL_BLE_SVC_DIS_SERIAL_NUMBER_DEFAULT -#define MYNEWT_VAL_BLE_SVC_DIS_SERIAL_NUMBER_DEFAULT (NULL) +#define MYNEWT_VAL_BLE_SVC_DIS_SERIAL_NUMBER_DEFAULT ("0000") #endif /* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ -#ifndef MYNEWT_VAL_BLE_SVC_DIS_SERIAL_NUMBER_READ_PERM +#if CONFIG_BT_NIMBLE_SVC_DIS_SERIAL_NUMBER +#define MYNEWT_VAL_BLE_SVC_DIS_SERIAL_NUMBER_READ_PERM (0) +#else #define MYNEWT_VAL_BLE_SVC_DIS_SERIAL_NUMBER_READ_PERM (-1) #endif #ifndef MYNEWT_VAL_BLE_SVC_DIS_SOFTWARE_REVISION_DEFAULT -#define MYNEWT_VAL_BLE_SVC_DIS_SOFTWARE_REVISION_DEFAULT (NULL) +#define MYNEWT_VAL_BLE_SVC_DIS_SOFTWARE_REVISION_DEFAULT ("0000") #endif /* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ -#ifndef MYNEWT_VAL_BLE_SVC_DIS_SOFTWARE_REVISION_READ_PERM +#if CONFIG_BT_NIMBLE_SVC_DIS_SOFTWARE_REVISION +#define MYNEWT_VAL_BLE_SVC_DIS_SOFTWARE_REVISION_READ_PERM (0) +#else #define MYNEWT_VAL_BLE_SVC_DIS_SOFTWARE_REVISION_READ_PERM (-1) #endif @@ -1595,23 +1604,31 @@ #endif #ifndef MYNEWT_VAL_BLE_SVC_DIS_SYSTEM_ID_DEFAULT -#define MYNEWT_VAL_BLE_SVC_DIS_SYSTEM_ID_DEFAULT (NULL) +#define MYNEWT_VAL_BLE_SVC_DIS_SYSTEM_ID_DEFAULT ("00000000") #endif /* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ -#ifndef MYNEWT_VAL_BLE_SVC_DIS_SYSTEM_ID_READ_PERM +#if CONFIG_BT_NIMBLE_SVC_DIS_SYSTEM_ID +#define MYNEWT_VAL_BLE_SVC_DIS_SYSTEM_ID_READ_PERM (0) +#else #define MYNEWT_VAL_BLE_SVC_DIS_SYSTEM_ID_READ_PERM (-1) #endif #ifndef MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_DEFAULT -#define MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_DEFAULT (NULL) +#define MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_DEFAULT ("000000") #endif /* Value copied from BLE_SVC_DIS_DEFAULT_READ_PERM */ -#ifndef MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_READ_PERM +#if CONFIG_BT_NIMBLE_SVC_DIS_PNP_ID +#define MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_READ_PERM (0) +#else #define MYNEWT_VAL_BLE_SVC_DIS_PNP_ID_READ_PERM (-1) #endif +#ifndef MYNEWT_VAL_BLE_SVC_DIS_INCLUDED +#define MYNEWT_VAL_BLE_SVC_DIS_INCLUDED (CONFIG_BT_NIMBLE_SVC_DIS_INCLUDED) +#endif + /*** @apache-mynewt-nimble/nimble/host/services/gap */ #ifndef MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE #define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE CONFIG_BT_NIMBLE_SVC_GAP_APPEARANCE From 006a6b29f161818553bc7d9af827ae341da03903 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Thu, 18 Jul 2024 17:30:03 +0530 Subject: [PATCH 13/21] fix(nimble): Added option to enable code under Enhanced COC --- components/bt/host/nimble/Kconfig.in | 8 ++++++++ .../host/nimble/port/include/esp_nimble_cfg.h | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 1f89d82728..04555dd726 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -99,6 +99,14 @@ config BT_NIMBLE_L2CAP_COC_MAX_NUM help Defines maximum number of BLE Connection Oriented Channels. When set to (0), BLE COC is not compiled in +config BT_NIMBLE_L2CAP_ENHANCED_COC + bool "L2CAP Enhanced Connection Oriented Channel" + depends on BT_NIMBLE_ENABLED && (BT_NIMBLE_L2CAP_COC_MAX_NUM >= 1) + default 0 + help + Enable Enhanced Credit Based Flow Control Mode + + choice BT_NIMBLE_PINNED_TO_CORE_CHOICE prompt "The CPU core on which NimBLE host will run" depends on BT_NIMBLE_ENABLED && !FREERTOS_UNICORE diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index d1ff729add..f5fcc5da3f 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -479,6 +479,18 @@ /*** @apache-mynewt-nimble/nimble/host */ +#if CONFIG_BT_NIMBLE_L2CAP_ENHANCED_COC +#ifndef MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC +#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (CONFIG_BT_NIMBLE_L2CAP_ENHANCED_COC) +#else +#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM +#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (1) +#else +#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0) +#endif +#endif +#endif + #ifndef MYNEWT_VAL_BLE_DYNAMIC_SERVICE #define MYNEWT_VAL_BLE_DYNAMIC_SERVICE CONFIG_BT_NIMBLE_DYNAMIC_SERVICE #endif @@ -782,11 +794,6 @@ #define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE-8) #endif -#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM -#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (1) -#else -#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0) -#endif #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1) From cfe927007b96d8570b9360ae477434b3503dfb5d Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Wed, 28 Aug 2024 16:06:50 +0800 Subject: [PATCH 14/21] fix(nimble): fix and enable connection subrating --- components/bt/host/nimble/Kconfig.in | 7 +++ examples/bluetooth/nimble/bleprph/main/main.c | 43 ++++++++++++------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 04555dd726..79df2ea276 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -1022,3 +1022,10 @@ config BT_NIMBLE_EATT_CHAN_NUM depends on BT_NIMBLE_ENABLED help Defines the number of channels EATT bearers can use + +config BT_NIMBLE_SUBRATE + bool "Enable Subrate Change" + default n + depends on BT_NIMBLE_ENABLED + help + Enable connection subrate change feature diff --git a/examples/bluetooth/nimble/bleprph/main/main.c b/examples/bluetooth/nimble/bleprph/main/main.c index 6a1526387a..7dc96226cd 100644 --- a/examples/bluetooth/nimble/bleprph/main/main.c +++ b/examples/bluetooth/nimble/bleprph/main/main.c @@ -431,28 +431,39 @@ bleprph_gap_event(struct ble_gap_event *event, void *arg) event->pathloss_threshold.zone_entered); return 0; #endif + #if MYNEWT_VAL(BLE_EATT_CHAN_NUM) > 0 case BLE_GAP_EVENT_EATT: MODLOG_DFLT(INFO, "EATT %s : conn_handle=%d cid=%d", event->eatt.status ? "disconnected" : "connected", event->eatt.conn_handle, event->eatt.cid); - if (event->eatt.status) { - /* Abort if disconnected */ - return 0; - } - cids[bearers] = event->eatt.cid; - bearers += 1; - if (bearers != MYNEWT_VAL(BLE_EATT_CHAN_NUM)) { - /* Wait until all EATT bearers are connected before proceeding */ - return 0; - } - /* Set the default bearer to use for further procedures */ - rc = ble_att_set_default_bearer_using_cid(event->eatt.conn_handle, cids[0]); - if (rc != 0) { - MODLOG_DFLT(INFO, "Cannot set default EATT bearer, rc = %d\n", rc); - return rc; - } + if (event->eatt.status) { + /* Abort if disconnected */ + return 0; + } + cids[bearers] = event->eatt.cid; + bearers += 1; + if (bearers != MYNEWT_VAL(BLE_EATT_CHAN_NUM)) { + /* Wait until all EATT bearers are connected before proceeding */ + return 0; + } + /* Set the default bearer to use for further procedures */ + rc = ble_att_set_default_bearer_using_cid(event->eatt.conn_handle, cids[0]); + if (rc != 0) { + MODLOG_DFLT(INFO, "Cannot set default EATT bearer, rc = %d\n", rc); + return rc; + } + + return 0; +#endif + +#if MYNEWT_VAL(BLE_CONN_SUBRATING) + case BLE_GAP_EVENT_SUBRATE_CHANGE: + MODLOG_DFLT(INFO, "Subrate change event : conn_handle=%d status=%d factor=%d", + event->subrate_change.conn_handle, + event->subrate_change.status, + event->subrate_change.subrate_factor); return 0; #endif } From 1355375592ce8514637f51ff23571a0c6a30fa27 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Wed, 4 Sep 2024 22:59:13 +0800 Subject: [PATCH 15/21] feat(nimble): Added option in menuconfig to enable Secure Connections Only mode --- components/bt/host/nimble/Kconfig.in | 7 +++++++ .../bt/host/nimble/port/include/esp_nimble_cfg.h | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 79df2ea276..9ceb94ff20 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -229,6 +229,13 @@ config BT_NIMBLE_SM_SC_LVL 3. Authenticated pairing with encryption 4. Authenticated LE Secure Connections pairing with encryption using a 128-bit strength encryption key. +config BT_NIMBLE_SM_SC_ONLY + int "Enable Secure Connections Only Mode" + depends on BT_NIMBLE_SECURITY_ENABLE + default 0 + help + Enable Secure Connections Only Mode + config BT_NIMBLE_DEBUG bool "Enable extra runtime asserts and host debugging" default n diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index f5fcc5da3f..6d97f991fd 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -919,6 +919,22 @@ #define MYNEWT_VAL_BLE_SM_SC_LVL CONFIG_BT_NIMBLE_SM_SC_LVL #endif +#ifndef MYNEWT_VAL_BLE_SM_LVL +#ifdef CONFIG_BT_NIMBLE_SM_LVL +#define MYNEWT_VAL_BLE_SM_LVL CONFIG_BT_NIMBLE_SM_LVL +#else +#define MYNEWT_VAL_BLE_SM_LVL (0) +#endif +#endif + +#ifndef MYNEWT_VAL_BLE_SM_SC_ONLY +#ifdef CONFIG_BT_NIMBLE_SM_SC_ONLY +#define MYNEWT_VAL_BLE_SM_SC_ONLY CONFIG_BT_NIMBLE_SM_SC_ONLY +#else +#define MYNEWT_VAL_BLE_SM_SC_ONLY (0) +#endif +#endif + #ifndef MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST #define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (0) #endif From ab7d0558d2256d44676f4261e2c326872552b925 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Tue, 29 Oct 2024 11:45:57 +0530 Subject: [PATCH 16/21] feat(nimble): Added support for persisting csf characteristic for bonded devices --- components/bt/host/nimble/port/include/esp_nimble_cfg.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 6d97f991fd..0e8de67703 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -132,6 +132,10 @@ #define MYNEWT_VAL_BLE_GATT_CACHING_MAX_DSCS (CONFIG_BT_NIMBLE_GATT_CACHING_MAX_DSCS) #endif +#ifndef MYNEWT_VAL_BLE_GATT_CSFC_SIZE +#define MYNEWT_VAL_BLE_GATT_CSFC_SIZE (1) +#endif + #ifndef CONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES #define MYNEWT_VAL_BLE_MULTI_ADV_INSTANCES (1) #else @@ -959,6 +963,10 @@ #define MYNEWT_VAL_BLE_STORE_MAX_CCCDS CONFIG_BT_NIMBLE_MAX_CCCDS #endif +#ifndef MYNEWT_VAL_BLE_STORE_MAX_CSFCS +#define MYNEWT_VAL_BLE_STORE_MAX_CSFCS (CONFIG_BT_NIMBLE_MAX_CONNECTIONS) +#endif + #ifdef CONFIG_BT_NIMBLE_MAX_EADS #define MYNEWT_VAL_BLE_STORE_MAX_EADS CONFIG_BT_NIMBLE_MAX_EADS #endif From 5c18ac288a64a9e5d6aa4ddb18e4af9b3a47a388 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Tue, 29 Oct 2024 19:24:14 +0530 Subject: [PATCH 17/21] feat(nimble): Added option to disable automatically sending extra credits to peer --- .../host/nimble/port/include/esp_nimble_cfg.h | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 0e8de67703..6a4548fbe9 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -483,17 +483,11 @@ /*** @apache-mynewt-nimble/nimble/host */ -#if CONFIG_BT_NIMBLE_L2CAP_ENHANCED_COC -#ifndef MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC -#define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (CONFIG_BT_NIMBLE_L2CAP_ENHANCED_COC) -#else -#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM +#if CONFIG_BT_NIMBLE_L2CAP_ENHANCED_COC || CONFIG_BT_NIMBLE_EATT_CHAN_NUM #define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (1) #else #define MYNEWT_VAL_BLE_L2CAP_ENHANCED_COC (0) #endif -#endif -#endif #ifndef MYNEWT_VAL_BLE_DYNAMIC_SERVICE #define MYNEWT_VAL_BLE_DYNAMIC_SERVICE CONFIG_BT_NIMBLE_DYNAMIC_SERVICE @@ -798,7 +792,6 @@ #define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE-8) #endif - #ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS #define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1) #endif @@ -811,13 +804,13 @@ #define MYNEWT_VAL_BLE_L2CAP_RX_FRAG_TIMEOUT (30000) #endif -#ifndef MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS -#ifdef CONFIG_BT_NIMBLE_EATT_CHAN_NUM +#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM > CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM #define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (CONFIG_BT_NIMBLE_EATT_CHAN_NUM) +#elif CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM +#define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM) #else #define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (1) #endif -#endif #ifndef MYNEWT_VAL_BLE_MESH #ifdef CONFIG_BT_NIMBLE_MESH @@ -932,11 +925,7 @@ #endif #ifndef MYNEWT_VAL_BLE_SM_SC_ONLY -#ifdef CONFIG_BT_NIMBLE_SM_SC_ONLY -#define MYNEWT_VAL_BLE_SM_SC_ONLY CONFIG_BT_NIMBLE_SM_SC_ONLY -#else -#define MYNEWT_VAL_BLE_SM_SC_ONLY (0) -#endif +#define MYNEWT_VAL_BLE_SM_SC_ONLY (CONFIG_BT_NIMBLE_SM_SC_ONLY) #endif #ifndef MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST From 253b72254e96a8a52b119998eb1780b7c18137bf Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Tue, 5 Nov 2024 21:30:52 +0530 Subject: [PATCH 18/21] feat(nimble): Added ways to enable/disable some menuconfig options at runtime --- components/bt/host/nimble/port/include/esp_nimble_cfg.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 6a4548fbe9..540f5ae24a 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -778,7 +778,7 @@ #define MYNEWT_VAL_BLE_HS_SYSINIT_STAGE (200) #endif -#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM +#if CONFIG_BT_NIMBLE_EATT_CHAN_NUM > CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM #define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (CONFIG_BT_NIMBLE_EATT_CHAN_NUM) #else #ifndef CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM @@ -925,7 +925,11 @@ #endif #ifndef MYNEWT_VAL_BLE_SM_SC_ONLY +#ifdef CONFIG_BT_NIMBLE_SM_SC_ONLY #define MYNEWT_VAL_BLE_SM_SC_ONLY (CONFIG_BT_NIMBLE_SM_SC_ONLY) +#else +#define MYNEWT_VAL_BLE_SM_SC_ONLY (0) +#endif #endif #ifndef MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST From 7b5a7a5401161bad82f9bcabc858c5f675f6712f Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Thu, 7 Nov 2024 17:06:08 +0530 Subject: [PATCH 19/21] fix(nimble): Added 1. Option to disable automatic discovery when receiving out-of-sync 2. Fixed bugs related to robust caching --- components/bt/host/nimble/Kconfig.in | 8 ++++++++ components/bt/host/nimble/port/include/esp_nimble_cfg.h | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 9ceb94ff20..40d9afd292 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -691,6 +691,14 @@ config BT_NIMBLE_GATT_CACHING_MAX_DSCS help Set this option to set the upper limit on number of discriptors per connection to be cached. +config BT_NIMBLE_GATT_CACHING_DISABLE_AUTO + bool "Do not start discovery procedure automatically upon receiving Out of Sync" + depends on BT_NIMBLE_GATT_CACHING + default n + help + When client receives ATT out-of-sync error message, it will not automatically start the discovery procedure + to correct the invalid cache. + config BT_NIMBLE_WHITELIST_SIZE int "BLE white list size" depends on BT_NIMBLE_ENABLED diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 540f5ae24a..8de3dfa3ea 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -130,7 +130,14 @@ #define MYNEWT_VAL_BLE_GATT_CACHING_MAX_SVCS (CONFIG_BT_NIMBLE_GATT_CACHING_MAX_SVCS) #define MYNEWT_VAL_BLE_GATT_CACHING_MAX_CHRS (CONFIG_BT_NIMBLE_GATT_CACHING_MAX_CHRS) #define MYNEWT_VAL_BLE_GATT_CACHING_MAX_DSCS (CONFIG_BT_NIMBLE_GATT_CACHING_MAX_DSCS) -#endif + +#ifdef CONFIG_BT_NIMBLE_GATT_CACHING_DISABLE_AUTO +#define MYNEWT_VAL_BLE_GATT_CACHING_DISABLE_AUTO (CONFIG_BT_NIMBLE_GATT_CACHING_DISABLE_AUTO) +#else +#define MYNEWT_VAL_BLE_GATT_CACHING_DISABLE_AUTO (0) +#endif /* CONFIG_BT_NIMBLE_GATT_CACHING_DISABLE_AUTO */ + +#endif /* CONFIG_BT_NIMBLE_GATT_CACHING */ #ifndef MYNEWT_VAL_BLE_GATT_CSFC_SIZE #define MYNEWT_VAL_BLE_GATT_CSFC_SIZE (1) From 071f0f9a92f6efdab6e61f33a8605b7f471bb7fd Mon Sep 17 00:00:00 2001 From: Abhinav Kudnar Date: Thu, 29 Aug 2024 16:35:13 +0800 Subject: [PATCH 20/21] feat(nimble): Added PAwR support for nimble with IDF examples --- components/bt/host/nimble/Kconfig.in | 7 + .../host/nimble/port/include/esp_nimble_cfg.h | 9 + examples/bluetooth/.build-test-rules.yml | 16 + .../ble_pawr_adv/ble_pawr_adv/CMakeLists.txt | 6 + .../ble_pawr_adv/ble_pawr_adv/README.md | 75 ++++ .../ble_pawr_adv/main/CMakeLists.txt | 4 + .../ble_pawr_adv/main/Kconfig.projbuild | 27 ++ .../ble_pawr_adv/main/idf_component.yml | 3 + .../ble_pawr_adv/ble_pawr_adv/main/main.c | 220 ++++++++++++ .../ble_pawr_adv/sdkconfig.defaults | 35 ++ .../tutorial/BLE_periodic_adv_walkthrough.md | 320 ++++++++++++++++++ .../ble_pawr_adv/ble_pawr_sync/CMakeLists.txt | 6 + .../ble_pawr_adv/ble_pawr_sync/README.md | 75 ++++ .../ble_pawr_sync/main/CMakeLists.txt | 4 + .../ble_pawr_sync/main/Kconfig.projbuild | 27 ++ .../ble_pawr_sync/main/idf_component.yml | 3 + .../ble_pawr_adv/ble_pawr_sync/main/main.c | 245 ++++++++++++++ .../ble_pawr_sync/sdkconfig.defaults | 35 ++ .../tutorial/BLE_periodic_adv_walkthrough.md | 320 ++++++++++++++++++ .../ble_pawr_adv_conn/CMakeLists.txt | 6 + .../ble_pawr_adv_conn/README.md | 75 ++++ .../ble_pawr_adv_conn/main/CMakeLists.txt | 4 + .../ble_pawr_adv_conn/main/Kconfig.projbuild | 27 ++ .../ble_pawr_adv_conn/main/idf_component.yml | 3 + .../ble_pawr_adv_conn/main/main.c | 285 ++++++++++++++++ .../ble_pawr_adv_conn/sdkconfig.defaults | 35 ++ .../tutorial/BLE_periodic_adv_walkthrough.md | 320 ++++++++++++++++++ .../ble_pawr_sync_conn/CMakeLists.txt | 6 + .../ble_pawr_sync_conn/README.md | 75 ++++ .../ble_pawr_sync_conn/main/CMakeLists.txt | 4 + .../ble_pawr_sync_conn/main/Kconfig.projbuild | 27 ++ .../ble_pawr_sync_conn/main/idf_component.yml | 3 + .../ble_pawr_sync_conn/main/main.c | 292 ++++++++++++++++ .../ble_pawr_sync_conn/sdkconfig.defaults | 35 ++ .../tutorial/BLE_periodic_adv_walkthrough.md | 320 ++++++++++++++++++ 35 files changed, 2954 insertions(+) create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/README.md create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/Kconfig.projbuild create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/idf_component.yml create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/main.c create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/sdkconfig.defaults create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/tutorial/BLE_periodic_adv_walkthrough.md create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/README.md create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/Kconfig.projbuild create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/idf_component.yml create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/main.c create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/sdkconfig.defaults create mode 100644 examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/tutorial/BLE_periodic_adv_walkthrough.md create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/README.md create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/Kconfig.projbuild create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/idf_component.yml create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/main.c create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/sdkconfig.defaults create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/tutorial/BLE_periodic_adv_walkthrough.md create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/README.md create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/CMakeLists.txt create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/Kconfig.projbuild create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/idf_component.yml create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/main.c create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/sdkconfig.defaults create mode 100644 examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/tutorial/BLE_periodic_adv_walkthrough.md diff --git a/components/bt/host/nimble/Kconfig.in b/components/bt/host/nimble/Kconfig.in index 40d9afd292..53ff2e872d 100644 --- a/components/bt/host/nimble/Kconfig.in +++ b/components/bt/host/nimble/Kconfig.in @@ -659,6 +659,13 @@ if BT_NIMBLE_50_FEATURE_SUPPORT default n help Set this option to enable the Power Control feature + + config BT_NIMBLE_PERIODIC_ADV_WITH_RESPONSES + bool "Enable Periodic Advertisement with Response (EXPERIMENTAL)" + depends on BT_NIMBLE_ENABLE_PERIODIC_ADV + default n + help + This enables controller PAwR (Periodic Advertisement with Response). endif menuconfig BT_NIMBLE_GATT_CACHING diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 8de3dfa3ea..2eaae6ee8a 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -1867,4 +1867,13 @@ #else #define MYNEWT_VAL_BLE_HOST_ALLOW_CONNECT_WITH_SCAN (0) #endif + +#ifndef MYNEWT_VAL_BLE_PERIODIC_ADV_WITH_RESPONSES +#ifdef CONFIG_BT_NIMBLE_PERIODIC_ADV_WITH_RESPONSES +#define MYNEWT_VAL_BLE_PERIODIC_ADV_WITH_RESPONSES (CONFIG_BT_NIMBLE_PERIODIC_ADV_WITH_RESPONSES) +#else +#define MYNEWT_VAL_BLE_PERIODIC_ADV_WITH_RESPONSES (0) +#endif +#endif + #endif diff --git a/examples/bluetooth/.build-test-rules.yml b/examples/bluetooth/.build-test-rules.yml index fb873f686c..60ac6f0e88 100644 --- a/examples/bluetooth/.build-test-rules.yml +++ b/examples/bluetooth/.build-test-rules.yml @@ -137,6 +137,22 @@ examples/bluetooth/nimble/ble_multi_adv: temporary: true reason: The runner doesn't support yet +examples/bluetooth/nimble/ble_pawr_adv: + enable: + - if: IDF_TARGET == ["esp32", "esp32s2", "esp32c3", "esp32s3", "esp32c2"] + +examples/bluetooth/nimble/ble_pawr_adv_conn: + enable: + - if: IDF_TARGET == ["esp32", "esp32s2", "esp32c3", "esp32s3", "esp32c2"] + +examples/bluetooth/nimble/ble_pawr_sync: + enable: + - if: IDF_TARGET == ["esp32", "esp32s2", "esp32c3", "esp32s3", "esp32c2"] + +examples/bluetooth/nimble/ble_pawr_sync_conn: + enable: + - if: IDF_TARGET == ["esp32", "esp32s2", "esp32c3", "esp32s3", "esp32c2"] + examples/bluetooth/nimble/ble_periodic_adv: enable: - if: IDF_TARGET in ["esp32c2", "esp32c3", "esp32s3" ] diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/CMakeLists.txt b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/CMakeLists.txt new file mode 100644 index 0000000000..69ca4023b1 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_pawr_adv) diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/README.md b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/README.md new file mode 100644 index 0000000000..fac8917b06 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/README.md @@ -0,0 +1,75 @@ +| Supported Targets | ESP32 | ESP32S2 | ESP32-C3 | ESP32-S3 | ESP32-C2 | +| ----------------- | ----- | ------- | -------- | -------- | -------- | + +# BLE Periodic Advertiser Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example starts periodic advertising with non resolvable private address. + +It uses Bluetooth controller and NimBLE stack based BLE host. + +This example aims at understanding periodic advertisement and related NimBLE APIs. + + +To test this demo, any BLE Periodic Sync app can be used. + + +Note : + +* Make sure to run `python -m pip install --user -r $IDF_PATH/requirements.txt -r $IDF_PATH/tools/ble/requirements.txt` to install the dependency packages needed. +* Currently this Python utility is only supported on Linux (BLE communication is via BLuez + DBus). + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Configure the project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `Example Configuration` menu: + +* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +There is this console output when periodic_adv is started: + +``` +I (313) BTDM_INIT: BT controller compile version [2ee0168] +I (313) phy_init: phy_version 912,d001756,Jun 2 2022,16:28:07 +I (353) system_api: Base MAC address is not set +I (353) system_api: read default base MAC address from EFUSE +I (353) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:14:8e + +I (363) NimBLE_BLE_PERIODIC_ADV: BLE Host Task Started +I (373) NimBLE: Device Address: +I (373) NimBLE: d0:42:3a:95:84:05 +I (373) NimBLE: + +I (383) NimBLE: instance 1 started (periodic) +``` + +## Note +* Periodic sync transfer is not implemented for now. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/CMakeLists.txt b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/CMakeLists.txt new file mode 100644 index 0000000000..023dd5e462 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(srcs "main.c") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/Kconfig.projbuild b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/Kconfig.projbuild new file mode 100644 index 0000000000..c7b32ae16d --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/Kconfig.projbuild @@ -0,0 +1,27 @@ +menu "Example Configuration" + + config EXAMPLE_EXTENDED_ADV + bool + depends on SOC_BLE_50_SUPPORTED + default y if SOC_ESP_NIMBLE_CONTROLLER + select BT_NIMBLE_EXT_ADV + prompt "Enable Extended Adv" + help + Use this option to enable extended advertising in the example. + If you disable this option, ensure config BT_NIMBLE_EXT_ADV is + also disabled from Nimble stack menuconfig. + + config EXAMPLE_RANDOM_ADDR + bool + prompt "Advertise RANDOM Address" + help + Use this option to advertise a random address instead of public address + + config EXAMPLE_PERIODIC_ADV_ENH + bool + prompt "Enable Periodic Adv Enhancements" + depends on SOC_BLE_50_SUPPORTED && SOC_BLE_PERIODIC_ADV_ENH_SUPPORTED + select BT_NIMBLE_PERIODIC_ADV_ENH + help + Use this option to enable periodic advertising enhancements +endmenu diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/idf_component.yml b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/idf_component.yml new file mode 100644 index 0000000000..d6e735fe77 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + nimble_peripheral_utils: + path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/main.c b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/main.c new file mode 100644 index 0000000000..3695bb366c --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/main/main.c @@ -0,0 +1,220 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" + +#define BLE_PAWR_NUM_SUBEVTS (5) +#define BLE_PAWR_SUB_INTERVAL (12) /*!< Interval between subevents (N * 1.25 ms) */ +#define BLE_PAWR_RSP_SLOT_DELAY (1) /*!< The first response slot delay (N * 1.25 ms)*/ +#define BLE_PAWR_RSP_SLOT_SPACING (32) /*!< Time between response slots (N * 0.125 ms) */ +#define BLE_PAWR_NUM_RSP_SLOTS (3) /*!< Number of subevent response slots */ +#define BLE_PAWR_SUB_DATA_LEN (10) + +#define TAG "NimBLE_BLE_PAwR" + +static struct ble_gap_set_periodic_adv_subev_data_params sub_data_params[BLE_PAWR_NUM_SUBEVTS]; +static uint8_t sub_data_pattern[BLE_PAWR_SUB_DATA_LEN] = {0}; + +static int +gap_event_cb(struct ble_gap_event *event, void *arg) +{ + int rc; + uint8_t sub; + uint8_t sent_num; + struct os_mbuf *data; + + switch (event->type) { + + case BLE_GAP_EVENT_PER_SUBEV_DATA_REQ: + ESP_LOGI(TAG, "[Request] data: %x, subevt start:%d, subevt count:%d", + sub_data_pattern[0], + event->periodic_adv_subev_data_req.subevent_start, + event->periodic_adv_subev_data_req.subevent_data_count); + + sent_num = event->periodic_adv_subev_data_req.subevent_data_count; + for (uint8_t i = 0; i < sent_num; i++) { + data = os_msys_get_pkthdr(BLE_PAWR_SUB_DATA_LEN, 0); + if (!data) { + ESP_LOGE(TAG, "No memory, %d", i); + break; + } + sub = (i + event->periodic_adv_subev_data_req.subevent_start) % BLE_PAWR_NUM_SUBEVTS; + memset(&sub_data_pattern[1], sub, BLE_PAWR_SUB_DATA_LEN - 1); + os_mbuf_append(data, sub_data_pattern, BLE_PAWR_SUB_DATA_LEN); + sub_data_params[i].subevent = sub; + sub_data_params[i].response_slot_start = 0; + sub_data_params[i].response_slot_count = BLE_PAWR_NUM_RSP_SLOTS; + sub_data_params[i].data = data; + sub_data_pattern[0]++; + } + + rc = ble_gap_set_periodic_adv_subev_data(event->periodic_adv_subev_data_req.adv_handle, + sent_num, sub_data_params); + if (rc) { + ESP_LOGE(TAG, "Failed to set Subevent Data, rc = 0x%x", rc); + } + return 0; + + case BLE_GAP_EVENT_PER_SUBEV_RESP: + + if (event->periodic_adv_response.data_status == BLE_GAP_PER_ADV_DATA_STATUS_INCOMPLETE) { + // ESP_LOGI(TAG,"Incomplete response report received, discarding \n"); + } + else if (event->periodic_adv_response.data_status == BLE_GAP_PER_ADV_DATA_STATUS_RX_FAILED) { + // ESP_LOGI(TAG,"Controller failed to received the AUX_SYNC_SUBEVENT_RSP\n"); + } + else if (event->periodic_adv_response.data_status == BLE_GAP_PER_ADV_DATA_STATUS_COMPLETE) { + ESP_LOGI(TAG, "[Response] subevent:%d, response_slot:%d, data_length:%d, data:%x", + event->periodic_adv_response.subevent, + event->periodic_adv_response.response_slot, + event->periodic_adv_response.data_length, + event->periodic_adv_response.data[0]); + } + else if (event->periodic_adv_response.data_status == BLE_GAP_PER_ADV_DATA_STATUS_TRUNCATED) { + // ESP_LOGI(TAG,"Truncated response report received, discarding\n"); + } + else { + ESP_LOGE(TAG,"Invalid data status\n"); + } + + return 0; + + default: + return 0; + } +} + +static void +start_periodic_adv(void) +{ + int rc; + uint8_t addr[6]; + struct ble_gap_periodic_adv_params pparams; + struct ble_gap_ext_adv_params params; + struct ble_hs_adv_fields adv_fields; + struct os_mbuf *data; + uint8_t instance = 0; + +#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH) + struct ble_gap_periodic_adv_enable_params eparams; + memset(&eparams, 0, sizeof(eparams)); +#endif + + /* Get the local public address. */ + rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr, NULL); + assert (rc == 0); + + ESP_LOGI(TAG, "Device Address %02x:%02x:%02x:%02x:%02x:%02x", addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0]); + + /* For periodic we use instance with non-connectable advertising */ + memset (¶ms, 0, sizeof(params)); + params.own_addr_type = BLE_OWN_ADDR_PUBLIC; + params.primary_phy = BLE_HCI_LE_PHY_CODED; + params.secondary_phy = BLE_HCI_LE_PHY_1M; + params.sid = 0; + params.itvl_min = BLE_GAP_ADV_ITVL_MS(100); + params.itvl_max = BLE_GAP_ADV_ITVL_MS(100); + + rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, gap_event_cb, NULL); + assert (rc == 0); + + memset(&adv_fields, 0, sizeof(adv_fields)); + adv_fields.name = (const uint8_t *)"Nimble_PAwR"; + adv_fields.name_len = strlen((char *)adv_fields.name); + + /* mbuf chain will be increased if needed */ + data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0); + assert(data); + + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data); + assert(rc == 0); + + rc = ble_gap_ext_adv_set_data(instance, data); + assert(rc == 0); + + /* configure periodic advertising */ + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; + pparams.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(3000); + pparams.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(3000); + /* Configure the parameters of PAwR. */ + pparams.num_subevents = BLE_PAWR_NUM_SUBEVTS; + pparams.subevent_interval = BLE_GAP_PERIODIC_ITVL_MS(300); + pparams.response_slot_delay = BLE_GAP_PERIODIC_ITVL_MS(80); + pparams.response_slot_spacing = 0xFF; + pparams.num_response_slots = BLE_PAWR_NUM_RSP_SLOTS; + + rc = ble_gap_periodic_adv_configure(instance, &pparams); + assert(rc == 0); + + /* start periodic advertising */ +#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH) + eparams.include_adi = 1; + rc = ble_gap_periodic_adv_start(instance, &eparams); +#else + rc = ble_gap_periodic_adv_start(instance); +#endif + assert (rc == 0); + + /* start advertising */ + rc = ble_gap_ext_adv_start(instance, 0, 0); + assert (rc == 0); + + ESP_LOGI(TAG, "instance %u started (periodic)\n", instance); +} + +static void +on_reset(int reason) +{ + ESP_LOGE(TAG, "Resetting state; reason=%d\n", reason); +} + +static void +on_sync(void) +{ + /* Begin advertising. */ + start_periodic_adv(); +} + +void pawr_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +void +app_main(void) +{ + esp_err_t ret; + + /* Initialize NVS — it is used to store PHY calibration data */ + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to init nimble %d ", ret); + return; + } + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = on_reset; + ble_hs_cfg.sync_cb = on_sync; + + nimble_port_freertos_init(pawr_host_task); +} diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/sdkconfig.defaults b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/sdkconfig.defaults new file mode 100644 index 0000000000..cfb85a1c81 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/sdkconfig.defaults @@ -0,0 +1,35 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_PERIODIC_ADV_WITH_RESPONSES=y +CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1 +CONFIG_BT_NIMBLE_ROLE_CENTRAL=y +CONFIG_BT_NIMBLE_ROLE_OBSERVER=n + +CONFIG_BT_CONTROLLER_DISABLED=y + +# +# Host-controller Transport +# +CONFIG_BT_NIMBLE_TRANSPORT_UART_PORT=1 +CONFIG_UART_BAUDRATE_115200=y +CONFIG_BT_NIMBLE_UART_TX_PIN=20 +CONFIG_BT_NIMBLE_UART_RX_PIN=21 +CONFIG_UART_HW_FLOWCTRL_CTS_RTS=n +# CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=22 +# CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 +# end of Host-controller Transport +# end of NimBLE Options + +# C6 Nordic +# TX: 20 ---- RX +# RX: 21 ---- TX +# RTS: 22 ---- CTS +# CTS: 23 ---- RTS diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/tutorial/BLE_periodic_adv_walkthrough.md b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/tutorial/BLE_periodic_adv_walkthrough.md new file mode 100644 index 0000000000..a7c8dbfc46 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_adv/tutorial/BLE_periodic_adv_walkthrough.md @@ -0,0 +1,320 @@ +# BLE Periodic Advertisement Example Walkthrough + +## Introduction + +In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address. + +## Includes + +This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are: + +```c +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#include "services/gap/ble_svc_gap.h" +#include "periodic_adv.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "patterns.h" +``` +These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“periodic_adv.h”` which expose the BLE APIs required to implement this example. + +* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack. +* `nimble_port_freertos.h`: Initializes and enables nimble host task. +* `ble_hs.h`: Defines the functionalities to handle the host event +* `ble_svc_gap.h`:Defines the macros for device name, and device appearance and declares the function to set them. +* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined. + +## Main Entry Point + +The program’s entry point is the app_main() function: +```c +void +app_main(void) +{ + int rc; + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(tag, "Failed to init nimble %d ", ret); + return; + } + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = periodic_adv_on_reset; + ble_hs_cfg.sync_cb = periodic_adv_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble_periodic_adv"); + assert(rc == 0); + + /* XXX Need to have a template for store */ + ble_store_config_init(); + + nimble_port_freertos_init(periodic_adv_host_task); +} +``` +The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations. +```c +esp_err_t ret = nvs_flash_init(); +if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); +} +ESP_ERROR_CHECK( ret ); +``` + +## BT Controller and Stack Initialization + +The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions: + +```c +esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +ret = esp_bt_controller_init(&config_opts); +``` +Next, the controller is enabled in BLE Mode. + +```c +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); +``` +The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode. + +There are four Bluetooth modes supported: + +1. `ESP_BT_MODE_IDLE`: Bluetooth not running +2. `ESP_BT_MODE_BLE`: BLE mode +3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode +4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic) + +After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`: + +```c +esp_err_t esp_nimble_init(void) +{ + +#if !SOC_ESP_NIMBLE_CONTROLLER + /* Initialize the function pointers for OS porting */ + npl_freertos_funcs_init(); + + npl_freertos_mempool_init(); + + if(esp_nimble_hci_init() != ESP_OK) { + ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n"); + return ESP_FAIL; + } + + /* Initialize default event queue */ + ble_npl_eventq_init(&g_eventq_dflt); + + os_msys_init(); + + void ble_store_ram_init(void); + /* XXX Need to have a template for store */ + ble_store_ram_init(); +#endif + + /* Initialize the host */ + ble_hs_init(); + return ESP_OK; +} +``` + +The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status + +```c +ble_hs_cfg.reset_cb = periodic_adv_on_reset; +ble_hs_cfg.sync_cb = periodic_adv_on_sync; +ble_hs_cfg.store_status_cb = ble_store_util_status_rr; +``` + +The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function. +```c +rc = ble_svc_gap_device_name_set("nimble_periodic_adv"); +``` +main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material. +```c +/* XXX Need to have a template for store */ + ble_store_config_init(); +``` + +The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`. + +```c +nimble_port_freertos_init(periodic_adv_host_task); +``` +`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but to handle the default queue, it is easier to create a separate task. + +## Generation of non-resolvable private address + +In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`. + +```c +#if CONFIG_EXAMPLE_RANDOM_ADDR +static void +periodic_adv_set_addr(void) +{ + ble_addr_t addr; + int rc; + + /* generate new non-resolvable private address */ + rc = ble_hs_id_gen_rnd(0, &addr); + assert(rc == 0); + + /* set generated address */ + rc = ble_hs_id_set_rnd(addr.val); + + assert(rc == 0); +} +#endif +``` + +## Periodic Advertisement + +Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively. +Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value. + +## Need of Extended Advertisement in Periodic Advertisement + +Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement. + + +Below is the implementation to start periodic advertisement. + +```c +static void +start_periodic_adv(void) +{ + int rc; + struct ble_gap_periodic_adv_params pparams; + struct ble_gap_ext_adv_params params; + struct ble_hs_adv_fields adv_fields; + struct os_mbuf *data; + uint8_t instance = 1; + ble_addr_t addr; + + /* set random (NRPA) address for instance */ + rc = ble_hs_id_gen_rnd(1, &addr); + assert (rc == 0); + + MODLOG_DFLT(INFO, "Device Address: "); + print_addr(addr.val); + MODLOG_DFLT(INFO, "\n"); + + /* For periodic we use instance with non-connectable advertising */ + memset (¶ms, 0, sizeof(params)); + + /* advertise using random addr */ + params.own_addr_type = BLE_OWN_ADDR_RANDOM; + params.primary_phy = BLE_HCI_LE_PHY_1M; + params.secondary_phy = BLE_HCI_LE_PHY_2M; + params.sid = 2; + + /* configure instance 1 */ + rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, NULL, NULL); + assert (rc == 0); + + rc = ble_gap_ext_adv_set_addr(instance, &addr ); + assert (rc == 0); + + memset(&adv_fields, 0, sizeof(adv_fields)); + adv_fields.name = (const uint8_t *)"Periodic ADV"; + adv_fields.name_len = strlen((char *)adv_fields.name); + + /* Default to legacy PDUs size, mbuf chain will be increased if needed + */ + data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0); + assert(data); + + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data); + assert(rc == 0); + + rc = ble_gap_ext_adv_set_data(instance, data); + assert(rc == 0); + + /* configure periodic advertising */ + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; + pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); + pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); + + rc = ble_gap_periodic_adv_configure(instance, &pparams); + assert(rc == 0); + + data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0); + assert(data); + + rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data)); + assert(rc == 0); + rc = ble_gap_periodic_adv_set_data(instance, data); + assert (rc == 0); + + /* start periodic advertising */ + assert (rc == 0 rc = ble_gap_periodic_adv_start(instance); +); + + /* start advertising */ + rc = ble_gap_ext_adv_start(instance, 0, 0); + assert (rc == 0); + + MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance); +} +``` + +The periodic advertisement uses a non-connectable advertising mode. `memset (¶ms, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0. + +## Parameter Configuration + +The below snippets represent the parameter configuration for extended and periodic advertisement. + +### For Extended Advertisement + +```c + params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random + params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M + params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M + params.sid = 2; // Advertising set Id is assigned with value 2. +``` + +### For Periodic Advertisement + +```c + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU + pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms + pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms +``` + +Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines. + +```c + struct ble_hci_le_set_periodic_adv_enable_cp cmd; + cmd.enable = 0x01; + cmd.adv_handle = instance; +``` + +Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively. + +Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration. + +max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit. + +## Conclusion + +This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough. + +1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time. +2. Periodic advertisement uses NRPA (Non Resolvable private address). It is a randomly generated address that changes periodically to prevent long-term tracking of a device. +3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time. \ No newline at end of file diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/CMakeLists.txt b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/CMakeLists.txt new file mode 100644 index 0000000000..39e048fcc9 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_pawr_sync) diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/README.md b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/README.md new file mode 100644 index 0000000000..fac8917b06 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/README.md @@ -0,0 +1,75 @@ +| Supported Targets | ESP32 | ESP32S2 | ESP32-C3 | ESP32-S3 | ESP32-C2 | +| ----------------- | ----- | ------- | -------- | -------- | -------- | + +# BLE Periodic Advertiser Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example starts periodic advertising with non resolvable private address. + +It uses Bluetooth controller and NimBLE stack based BLE host. + +This example aims at understanding periodic advertisement and related NimBLE APIs. + + +To test this demo, any BLE Periodic Sync app can be used. + + +Note : + +* Make sure to run `python -m pip install --user -r $IDF_PATH/requirements.txt -r $IDF_PATH/tools/ble/requirements.txt` to install the dependency packages needed. +* Currently this Python utility is only supported on Linux (BLE communication is via BLuez + DBus). + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Configure the project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `Example Configuration` menu: + +* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +There is this console output when periodic_adv is started: + +``` +I (313) BTDM_INIT: BT controller compile version [2ee0168] +I (313) phy_init: phy_version 912,d001756,Jun 2 2022,16:28:07 +I (353) system_api: Base MAC address is not set +I (353) system_api: read default base MAC address from EFUSE +I (353) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:14:8e + +I (363) NimBLE_BLE_PERIODIC_ADV: BLE Host Task Started +I (373) NimBLE: Device Address: +I (373) NimBLE: d0:42:3a:95:84:05 +I (373) NimBLE: + +I (383) NimBLE: instance 1 started (periodic) +``` + +## Note +* Periodic sync transfer is not implemented for now. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/CMakeLists.txt b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/CMakeLists.txt new file mode 100644 index 0000000000..023dd5e462 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(srcs "main.c") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/Kconfig.projbuild b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/Kconfig.projbuild new file mode 100644 index 0000000000..c7b32ae16d --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/Kconfig.projbuild @@ -0,0 +1,27 @@ +menu "Example Configuration" + + config EXAMPLE_EXTENDED_ADV + bool + depends on SOC_BLE_50_SUPPORTED + default y if SOC_ESP_NIMBLE_CONTROLLER + select BT_NIMBLE_EXT_ADV + prompt "Enable Extended Adv" + help + Use this option to enable extended advertising in the example. + If you disable this option, ensure config BT_NIMBLE_EXT_ADV is + also disabled from Nimble stack menuconfig. + + config EXAMPLE_RANDOM_ADDR + bool + prompt "Advertise RANDOM Address" + help + Use this option to advertise a random address instead of public address + + config EXAMPLE_PERIODIC_ADV_ENH + bool + prompt "Enable Periodic Adv Enhancements" + depends on SOC_BLE_50_SUPPORTED && SOC_BLE_PERIODIC_ADV_ENH_SUPPORTED + select BT_NIMBLE_PERIODIC_ADV_ENH + help + Use this option to enable periodic advertising enhancements +endmenu diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/idf_component.yml b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/idf_component.yml new file mode 100644 index 0000000000..d6e735fe77 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + nimble_peripheral_utils: + path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/main.c b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/main.c new file mode 100644 index 0000000000..11ba92ad04 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/main/main.c @@ -0,0 +1,245 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" + +#define TAG "NimBLE_BLE_PAwR" +#define TARGET_NAME "Nimble_PAwR" +#define BLE_PAWR_RSP_DATA_LEN (20) +static uint8_t sub_data_pattern[BLE_PAWR_RSP_DATA_LEN] = {0}; + +static int create_periodic_sync(struct ble_gap_ext_disc_desc *disc); +static void start_scan(void); + +static struct ble_hs_adv_fields fields; +static bool synced = false; + +static int +gap_event_cb(struct ble_gap_event *event, void *arg) +{ + int rc; + uint8_t *addr; + struct ble_gap_ext_disc_desc *disc; + + switch (event->type) { + case BLE_GAP_EVENT_EXT_DISC: + disc = &event->ext_disc; + addr = disc->addr.val; + ESP_LOGI(TAG, "[Disc advertiser] addr %02x:%02x:%02x:%02x:%02x:%02x, props: 0x%x, rssi:%d", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0], disc->props, disc->rssi); + if (synced) { + return 0; + } + + rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + if (rc != 0) { + ESP_LOGE(TAG, "Failed to parse adv data, rc = %d", rc); + return 0; + } + + if (disc->periodic_adv_itvl && fields.name_len && !memcmp(fields.name, TARGET_NAME, strlen(TARGET_NAME))) { + create_periodic_sync(disc); + } + return 0; + + case BLE_GAP_EVENT_PERIODIC_REPORT: + if (event->periodic_report.event_counter % 10 == 0) { + // print every 10th event + ESP_LOGI(TAG, "[Periodic Adv Report] handle:%d, rssi:%d, data status:0x%x", + event->periodic_report.sync_handle, event->periodic_report.rssi, + event->periodic_report.data_status); + ESP_LOGI(TAG, "[Periodic Adv Report] event_counter(%d), subevent(%d)", + event->periodic_report.event_counter, event->periodic_report.subevent); + } + + struct ble_gap_periodic_adv_response_params param = { + .request_event = event->periodic_report.event_counter, + .request_subevent = event->periodic_report.subevent, + .response_subevent = event->periodic_report.subevent, + .response_slot = 0 + }; + + struct os_mbuf *data = os_msys_get_pkthdr(BLE_PAWR_RSP_DATA_LEN, 0); + if (!data) { + ESP_LOGE(TAG, "No memory"); + return 0; + } + // create a special data for checking manually in ADV side + sub_data_pattern[0] = event->periodic_report.data[0]; + memset(sub_data_pattern + 1, event->periodic_report.subevent, BLE_PAWR_RSP_DATA_LEN - 1); + os_mbuf_append(data, sub_data_pattern, BLE_PAWR_RSP_DATA_LEN); + + rc = ble_gap_periodic_adv_set_response_data(event->periodic_report.sync_handle, ¶m, data); + if (rc) { + ESP_LOGE(TAG, "Set response data failed, subev(%x), rsp_slot(%d), rc(0x%x)", + sub_data_pattern[0], event->periodic_report.subevent, rc); + } + os_mbuf_free_chain(data); + + return 0; + + case BLE_GAP_EVENT_PERIODIC_SYNC_LOST: + ESP_LOGE(TAG, "[Periodic Sync Lost] handle:%d, Reason = 0x%x", + event->periodic_sync_lost.sync_handle, event->periodic_sync_lost.reason); + synced = false; + start_scan(); + return 0; + + case BLE_GAP_EVENT_PERIODIC_SYNC: + if (!event->periodic_sync.status) { + ESP_LOGI(TAG, "[Periodic Sync Established] sync handle:%d, num_subevents:0x%x", + event->periodic_sync.sync_handle, event->periodic_sync.num_subevents); + ESP_LOGI(TAG, "subevent_interval:0x%x, slot_delay:0x%x,slot_spacing:0x%x", + event->periodic_sync.subevent_interval, + event->periodic_sync.response_slot_delay, + event->periodic_sync.response_slot_spacing); + ble_gap_disc_cancel(); + + // choose subevents in range 0 to (num_subevents - 1) + uint8_t subevents[] = {0, 1, 2, 3, 4}; + int result = ble_gap_periodic_adv_sync_subev(event->periodic_sync.sync_handle, 0, sizeof(subevents), subevents); + if (result == ESP_OK) { + ESP_LOGI(TAG, "[Subevent Sync OK] sync handle:%d, sync_subevents:%d", event->periodic_sync.sync_handle, sizeof(subevents)); + } else { + ESP_LOGE(TAG, "Failed to sync subevents, rc = 0x%x", result); + } + } else { + ESP_LOGE(TAG, "Periodic Sync Error, status = %d", event->periodic_sync.status); + synced = false; + start_scan(); + } + return 0; + + default: + return 0; + } +} + +static int +create_periodic_sync(struct ble_gap_ext_disc_desc *disc) +{ + int rc; + struct ble_gap_periodic_sync_params params; + + params.skip = 0; + params.sync_timeout = 4000; + params.reports_disabled = 0; + +#if CONFIG_EXAMPLE_PERIODIC_ADV_ENH + /* This way the periodic advertising reports will not be + delivered to host unless the advertising data is changed + or the Data-Id is updated by the advertiser */ + params.filter_duplicates = 1; +#endif + + rc = ble_gap_periodic_adv_sync_create(&disc->addr, disc->sid, ¶ms, gap_event_cb, NULL); + if (!rc) { + synced = true; + ESP_LOGI(TAG, "Create sync"); + } else { + ESP_LOGE(TAG, "Failed to create sync, rc = %d", rc); + } + + return rc; +} + +static void +start_scan(void) +{ + int rc; + struct ble_gap_ext_disc_params disc_params; + + /* Perform a passive scan. I.e., don't send follow-up scan requests to + * each advertiser. + */ + disc_params.itvl = BLE_GAP_SCAN_ITVL_MS(600); + disc_params.window = BLE_GAP_SCAN_ITVL_MS(300); + disc_params.passive = 1; + + /* Tell the controller to filter duplicates; we don't want to process + * repeated advertisements from the same device. + */ + rc = ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0, NULL, &disc_params, + gap_event_cb, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Error initiating GAP discovery procedure; rc=%d\n", rc); + } +} + +static void +on_reset(int reason) +{ + ESP_LOGE(TAG, "Resetting state; reason=%d\n", reason); +} + +/* Cnnot find `ble_single_xxxx()`, workaround */ +// static void +// on_sync(void) +// { +// int ble_single_env_init(void); +// int ble_single_init(void); + +// int rc; + +// rc = ble_single_env_init(); +// assert(!rc); +// rc = ble_single_init(); +// assert(!rc); + +// start_scan(); +// } + +static void +on_sync(void) +{ + int rc; + + /* Make sure we have proper identity address set (public preferred) */ + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + start_scan(); +} + +void pawr_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +void +app_main(void) +{ + esp_err_t ret; + + /* Initialize NVS — it is used to store PHY calibration data */ + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to init nimble %d ", ret); + return; + } + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = on_reset; + ble_hs_cfg.sync_cb = on_sync; + + nimble_port_freertos_init(pawr_host_task); +} diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/sdkconfig.defaults b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/sdkconfig.defaults new file mode 100644 index 0000000000..83db34e62f --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/sdkconfig.defaults @@ -0,0 +1,35 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_EXT_ADV=n +CONFIG_BT_NIMBLE_PERIODIC_ADV_WITH_RESPONSES=y +CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1 +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=y + +CONFIG_BT_CONTROLLER_DISABLED=y + +# +# Host-controller Transport +# +CONFIG_BT_NIMBLE_TRANSPORT_UART_PORT=1 +CONFIG_UART_BAUDRATE_115200=y +CONFIG_BT_NIMBLE_UART_TX_PIN=20 +CONFIG_BT_NIMBLE_UART_RX_PIN=21 +CONFIG_UART_HW_FLOWCTRL_CTS_RTS=n +# CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=22 +# CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 +# end of Host-controller Transport +# end of NimBLE Options + +# C6 Nordic +# TX: 20 ---- RX +# RX: 21 ---- TX +# RTS: 22 ---- CTS +# CTS: 23 ---- RTS diff --git a/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/tutorial/BLE_periodic_adv_walkthrough.md b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/tutorial/BLE_periodic_adv_walkthrough.md new file mode 100644 index 0000000000..a7c8dbfc46 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv/ble_pawr_sync/tutorial/BLE_periodic_adv_walkthrough.md @@ -0,0 +1,320 @@ +# BLE Periodic Advertisement Example Walkthrough + +## Introduction + +In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address. + +## Includes + +This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are: + +```c +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#include "services/gap/ble_svc_gap.h" +#include "periodic_adv.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "patterns.h" +``` +These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“periodic_adv.h”` which expose the BLE APIs required to implement this example. + +* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack. +* `nimble_port_freertos.h`: Initializes and enables nimble host task. +* `ble_hs.h`: Defines the functionalities to handle the host event +* `ble_svc_gap.h`:Defines the macros for device name, and device appearance and declares the function to set them. +* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined. + +## Main Entry Point + +The program’s entry point is the app_main() function: +```c +void +app_main(void) +{ + int rc; + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(tag, "Failed to init nimble %d ", ret); + return; + } + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = periodic_adv_on_reset; + ble_hs_cfg.sync_cb = periodic_adv_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble_periodic_adv"); + assert(rc == 0); + + /* XXX Need to have a template for store */ + ble_store_config_init(); + + nimble_port_freertos_init(periodic_adv_host_task); +} +``` +The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations. +```c +esp_err_t ret = nvs_flash_init(); +if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); +} +ESP_ERROR_CHECK( ret ); +``` + +## BT Controller and Stack Initialization + +The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions: + +```c +esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +ret = esp_bt_controller_init(&config_opts); +``` +Next, the controller is enabled in BLE Mode. + +```c +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); +``` +The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode. + +There are four Bluetooth modes supported: + +1. `ESP_BT_MODE_IDLE`: Bluetooth not running +2. `ESP_BT_MODE_BLE`: BLE mode +3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode +4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic) + +After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`: + +```c +esp_err_t esp_nimble_init(void) +{ + +#if !SOC_ESP_NIMBLE_CONTROLLER + /* Initialize the function pointers for OS porting */ + npl_freertos_funcs_init(); + + npl_freertos_mempool_init(); + + if(esp_nimble_hci_init() != ESP_OK) { + ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n"); + return ESP_FAIL; + } + + /* Initialize default event queue */ + ble_npl_eventq_init(&g_eventq_dflt); + + os_msys_init(); + + void ble_store_ram_init(void); + /* XXX Need to have a template for store */ + ble_store_ram_init(); +#endif + + /* Initialize the host */ + ble_hs_init(); + return ESP_OK; +} +``` + +The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status + +```c +ble_hs_cfg.reset_cb = periodic_adv_on_reset; +ble_hs_cfg.sync_cb = periodic_adv_on_sync; +ble_hs_cfg.store_status_cb = ble_store_util_status_rr; +``` + +The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function. +```c +rc = ble_svc_gap_device_name_set("nimble_periodic_adv"); +``` +main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material. +```c +/* XXX Need to have a template for store */ + ble_store_config_init(); +``` + +The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`. + +```c +nimble_port_freertos_init(periodic_adv_host_task); +``` +`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but to handle the default queue, it is easier to create a separate task. + +## Generation of non-resolvable private address + +In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`. + +```c +#if CONFIG_EXAMPLE_RANDOM_ADDR +static void +periodic_adv_set_addr(void) +{ + ble_addr_t addr; + int rc; + + /* generate new non-resolvable private address */ + rc = ble_hs_id_gen_rnd(0, &addr); + assert(rc == 0); + + /* set generated address */ + rc = ble_hs_id_set_rnd(addr.val); + + assert(rc == 0); +} +#endif +``` + +## Periodic Advertisement + +Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively. +Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value. + +## Need of Extended Advertisement in Periodic Advertisement + +Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement. + + +Below is the implementation to start periodic advertisement. + +```c +static void +start_periodic_adv(void) +{ + int rc; + struct ble_gap_periodic_adv_params pparams; + struct ble_gap_ext_adv_params params; + struct ble_hs_adv_fields adv_fields; + struct os_mbuf *data; + uint8_t instance = 1; + ble_addr_t addr; + + /* set random (NRPA) address for instance */ + rc = ble_hs_id_gen_rnd(1, &addr); + assert (rc == 0); + + MODLOG_DFLT(INFO, "Device Address: "); + print_addr(addr.val); + MODLOG_DFLT(INFO, "\n"); + + /* For periodic we use instance with non-connectable advertising */ + memset (¶ms, 0, sizeof(params)); + + /* advertise using random addr */ + params.own_addr_type = BLE_OWN_ADDR_RANDOM; + params.primary_phy = BLE_HCI_LE_PHY_1M; + params.secondary_phy = BLE_HCI_LE_PHY_2M; + params.sid = 2; + + /* configure instance 1 */ + rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, NULL, NULL); + assert (rc == 0); + + rc = ble_gap_ext_adv_set_addr(instance, &addr ); + assert (rc == 0); + + memset(&adv_fields, 0, sizeof(adv_fields)); + adv_fields.name = (const uint8_t *)"Periodic ADV"; + adv_fields.name_len = strlen((char *)adv_fields.name); + + /* Default to legacy PDUs size, mbuf chain will be increased if needed + */ + data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0); + assert(data); + + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data); + assert(rc == 0); + + rc = ble_gap_ext_adv_set_data(instance, data); + assert(rc == 0); + + /* configure periodic advertising */ + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; + pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); + pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); + + rc = ble_gap_periodic_adv_configure(instance, &pparams); + assert(rc == 0); + + data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0); + assert(data); + + rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data)); + assert(rc == 0); + rc = ble_gap_periodic_adv_set_data(instance, data); + assert (rc == 0); + + /* start periodic advertising */ + assert (rc == 0 rc = ble_gap_periodic_adv_start(instance); +); + + /* start advertising */ + rc = ble_gap_ext_adv_start(instance, 0, 0); + assert (rc == 0); + + MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance); +} +``` + +The periodic advertisement uses a non-connectable advertising mode. `memset (¶ms, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0. + +## Parameter Configuration + +The below snippets represent the parameter configuration for extended and periodic advertisement. + +### For Extended Advertisement + +```c + params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random + params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M + params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M + params.sid = 2; // Advertising set Id is assigned with value 2. +``` + +### For Periodic Advertisement + +```c + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU + pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms + pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms +``` + +Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines. + +```c + struct ble_hci_le_set_periodic_adv_enable_cp cmd; + cmd.enable = 0x01; + cmd.adv_handle = instance; +``` + +Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively. + +Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration. + +max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit. + +## Conclusion + +This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough. + +1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time. +2. Periodic advertisement uses NRPA (Non Resolvable private address). It is a randomly generated address that changes periodically to prevent long-term tracking of a device. +3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time. \ No newline at end of file diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/CMakeLists.txt b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/CMakeLists.txt new file mode 100644 index 0000000000..6cba0c9d84 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_pawr_adv_conn) diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/README.md b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/README.md new file mode 100644 index 0000000000..fac8917b06 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/README.md @@ -0,0 +1,75 @@ +| Supported Targets | ESP32 | ESP32S2 | ESP32-C3 | ESP32-S3 | ESP32-C2 | +| ----------------- | ----- | ------- | -------- | -------- | -------- | + +# BLE Periodic Advertiser Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example starts periodic advertising with non resolvable private address. + +It uses Bluetooth controller and NimBLE stack based BLE host. + +This example aims at understanding periodic advertisement and related NimBLE APIs. + + +To test this demo, any BLE Periodic Sync app can be used. + + +Note : + +* Make sure to run `python -m pip install --user -r $IDF_PATH/requirements.txt -r $IDF_PATH/tools/ble/requirements.txt` to install the dependency packages needed. +* Currently this Python utility is only supported on Linux (BLE communication is via BLuez + DBus). + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Configure the project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `Example Configuration` menu: + +* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +There is this console output when periodic_adv is started: + +``` +I (313) BTDM_INIT: BT controller compile version [2ee0168] +I (313) phy_init: phy_version 912,d001756,Jun 2 2022,16:28:07 +I (353) system_api: Base MAC address is not set +I (353) system_api: read default base MAC address from EFUSE +I (353) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:14:8e + +I (363) NimBLE_BLE_PERIODIC_ADV: BLE Host Task Started +I (373) NimBLE: Device Address: +I (373) NimBLE: d0:42:3a:95:84:05 +I (373) NimBLE: + +I (383) NimBLE: instance 1 started (periodic) +``` + +## Note +* Periodic sync transfer is not implemented for now. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/CMakeLists.txt b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/CMakeLists.txt new file mode 100644 index 0000000000..023dd5e462 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(srcs "main.c") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/Kconfig.projbuild b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/Kconfig.projbuild new file mode 100644 index 0000000000..c7b32ae16d --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/Kconfig.projbuild @@ -0,0 +1,27 @@ +menu "Example Configuration" + + config EXAMPLE_EXTENDED_ADV + bool + depends on SOC_BLE_50_SUPPORTED + default y if SOC_ESP_NIMBLE_CONTROLLER + select BT_NIMBLE_EXT_ADV + prompt "Enable Extended Adv" + help + Use this option to enable extended advertising in the example. + If you disable this option, ensure config BT_NIMBLE_EXT_ADV is + also disabled from Nimble stack menuconfig. + + config EXAMPLE_RANDOM_ADDR + bool + prompt "Advertise RANDOM Address" + help + Use this option to advertise a random address instead of public address + + config EXAMPLE_PERIODIC_ADV_ENH + bool + prompt "Enable Periodic Adv Enhancements" + depends on SOC_BLE_50_SUPPORTED && SOC_BLE_PERIODIC_ADV_ENH_SUPPORTED + select BT_NIMBLE_PERIODIC_ADV_ENH + help + Use this option to enable periodic advertising enhancements +endmenu diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/idf_component.yml b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/idf_component.yml new file mode 100644 index 0000000000..d6e735fe77 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + nimble_peripheral_utils: + path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/main.c b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/main.c new file mode 100644 index 0000000000..8d428352b1 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/main/main.c @@ -0,0 +1,285 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" + +#define BLE_PAWR_NUM_SUBEVTS (5) +#define BLE_PAWR_SUB_INTERVAL (12) /*!< Interval between subevents (N * 1.25 ms) */ +#define BLE_PAWR_RSP_SLOT_DELAY (1) /*!< The first response slot delay (N * 1.25 ms)*/ +#define BLE_PAWR_RSP_SLOT_SPACING (32) /*!< Time between response slots (N * 0.125 ms) */ +#define BLE_PAWR_NUM_RSP_SLOTS (3) /*!< Number of subevent response slots */ +#define BLE_PAWR_SUB_DATA_LEN (10) + +#define TAG "NimBLE_BLE_PAwR" + +static struct ble_gap_set_periodic_adv_subev_data_params sub_data_params[BLE_PAWR_NUM_SUBEVTS]; +static uint8_t sub_data_pattern[BLE_PAWR_SUB_DATA_LEN] = {0}; +static uint8_t conn; +static struct ble_gap_conn_desc desc; +char * +addr_str(const void *addr) +{ + static char buf[6 * 2 + 5 + 1]; + const uint8_t *u8p; + + u8p = addr; + sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", + u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); + + return buf; +} + +void +print_conn_desc(const struct ble_gap_conn_desc *desc) +{ + ESP_LOGI(TAG,"handle=%d our_ota_addr_type=%d our_ota_addr=%s ", + desc->conn_handle, desc->our_ota_addr.type, + addr_str(desc->our_ota_addr.val)); + ESP_LOGI(TAG, "our_id_addr_type=%d our_id_addr=%s ", + desc->our_id_addr.type, addr_str(desc->our_id_addr.val)); + ESP_LOGI(TAG, "peer_ota_addr_type=%d peer_ota_addr=%s ", + desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val)); + ESP_LOGI(TAG, "peer_id_addr_type=%d peer_id_addr=%s ", + desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val)); + ESP_LOGI(TAG, "conn_itvl=%d conn_latency=%d supervision_timeout=%d " + "encrypted=%d authenticated=%d bonded=%d\n", + desc->conn_itvl, desc->conn_latency, + desc->supervision_timeout, + desc->sec_state.encrypted, + desc->sec_state.authenticated, + desc->sec_state.bonded); +} + + +static int +gap_event_cb(struct ble_gap_event *event, void *arg) +{ + int rc; + uint8_t sub; + uint8_t sent_num; + struct os_mbuf *data; + ble_addr_t peer_addr; + uint8_t adv_handle; + uint8_t subevent; + uint8_t phy_mask; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + printf("\n"); + ESP_LOGI(TAG, "Connection established, conn_handle = 0x%02x, Adv handle = 0x%0x, status = 0x%0x\n",event->connect.conn_handle,event->connect.adv_handle, event->connect.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + if(rc == 0){ + print_conn_desc(&desc); + } + else{ + ESP_LOGE(TAG,"Failed to find Conn Information"); + } + + return 0; + case BLE_GAP_EVENT_PER_SUBEV_DATA_REQ: + ESP_LOGI(TAG, "[Request] data: %x, subevt start:%d, subevt count:%d", + sub_data_pattern[0], + event->periodic_adv_subev_data_req.subevent_start, + event->periodic_adv_subev_data_req.subevent_data_count); + + sent_num = event->periodic_adv_subev_data_req.subevent_data_count; + for (uint8_t i = 0; i < sent_num; i++) { + data = os_msys_get_pkthdr(BLE_PAWR_SUB_DATA_LEN, 0); + if (!data) { + ESP_LOGE(TAG, "No memory, %d", i); + break; + } + sub = (i + event->periodic_adv_subev_data_req.subevent_start) % BLE_PAWR_NUM_SUBEVTS; + memset(&sub_data_pattern[1], sub, BLE_PAWR_SUB_DATA_LEN - 1); + os_mbuf_append(data, sub_data_pattern, BLE_PAWR_SUB_DATA_LEN); + sub_data_params[i].subevent = sub; + sub_data_params[i].response_slot_start = 0; + sub_data_params[i].response_slot_count = BLE_PAWR_NUM_RSP_SLOTS; + sub_data_params[i].data = data; + sub_data_pattern[0]++; + } + + rc = ble_gap_set_periodic_adv_subev_data(event->periodic_adv_subev_data_req.adv_handle, + sent_num, sub_data_params); + if (rc) { + ESP_LOGE(TAG, "Failed to set Subevent Data, rc = 0x%x", rc); + } + return 0; + + case BLE_GAP_EVENT_PER_SUBEV_RESP: + + if (event->periodic_adv_response.data_status == BLE_GAP_PER_ADV_DATA_STATUS_INCOMPLETE) { + // ESP_LOGI(TAG,"Incomplete response report received, discarding \n"); + } + else if (event->periodic_adv_response.data_status == BLE_GAP_PER_ADV_DATA_STATUS_RX_FAILED) { + // ESP_LOGI(TAG,"Controller failed to received the AUX_SYNC_SUBEVENT_RSP\n"); + } + else if (event->periodic_adv_response.data_status == BLE_GAP_PER_ADV_DATA_STATUS_COMPLETE) { + ESP_LOGI(TAG, "[Response] subevent:%d, response_slot:%d, data_length:%d, data:%x", + event->periodic_adv_response.subevent, + event->periodic_adv_response.response_slot, + event->periodic_adv_response.data_length, + event->periodic_adv_response.data[0]); + peer_addr.type=0; + memcpy(peer_addr.val,&event->periodic_adv_response.data[1],6); + + adv_handle = event->periodic_adv_response.adv_handle; + subevent = event->periodic_adv_response.subevent; + phy_mask = 0x01; + + if (conn == 0) { + rc = ble_gap_connect_with_synced(0,adv_handle,subevent,&peer_addr,30000,phy_mask,NULL,NULL,NULL,gap_event_cb,NULL); + if (rc != 0 ) { + ESP_LOGI(TAG,"Error: Failed to connect to device , rc = %d\n",rc); + } + conn = 1; + } + } + else if (event->periodic_adv_response.data_status == BLE_GAP_PER_ADV_DATA_STATUS_TRUNCATED) { + // ESP_LOGI(TAG,"Truncated response report received, discarding\n"); + } + else { + ESP_LOGE(TAG,"Invalid data status\n"); + } + + return 0; + + default: + return 0; + } +} + +static void +start_periodic_adv(void) +{ + int rc; + uint8_t addr[6]; + struct ble_gap_periodic_adv_params pparams; + struct ble_gap_ext_adv_params params; + struct ble_hs_adv_fields adv_fields; + struct os_mbuf *data; + uint8_t instance = 0; + +#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH) + struct ble_gap_periodic_adv_enable_params eparams; + memset(&eparams, 0, sizeof(eparams)); +#endif + + /* Get the local public address. */ + rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, addr, NULL); + assert (rc == 0); + + ESP_LOGI(TAG, "Device Address %02x:%02x:%02x:%02x:%02x:%02x", addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0]); + + /* For periodic we use instance with non-connectable advertising */ + memset (¶ms, 0, sizeof(params)); + params.own_addr_type = BLE_OWN_ADDR_PUBLIC; + params.primary_phy = BLE_HCI_LE_PHY_CODED; + params.secondary_phy = BLE_HCI_LE_PHY_1M; + params.sid = 0; + params.itvl_min = BLE_GAP_ADV_ITVL_MS(100); + params.itvl_max = BLE_GAP_ADV_ITVL_MS(100); + + rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, gap_event_cb, NULL); + assert (rc == 0); + + memset(&adv_fields, 0, sizeof(adv_fields)); + adv_fields.name = (const uint8_t *)"Nimble_PAwR"; + adv_fields.name_len = strlen((char *)adv_fields.name); + + /* mbuf chain will be increased if needed */ + data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0); + assert(data); + + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data); + assert(rc == 0); + + rc = ble_gap_ext_adv_set_data(instance, data); + assert(rc == 0); + + /* configure periodic advertising */ + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; + pparams.itvl_min = BLE_GAP_PERIODIC_ITVL_MS(3000); + pparams.itvl_max = BLE_GAP_PERIODIC_ITVL_MS(3000); + /* Configure the parameters of PAwR. */ + pparams.num_subevents = BLE_PAWR_NUM_SUBEVTS; + pparams.subevent_interval = BLE_GAP_PERIODIC_ITVL_MS(300); + pparams.response_slot_delay = BLE_GAP_PERIODIC_ITVL_MS(80); + pparams.response_slot_spacing = 0xFF; + pparams.num_response_slots = BLE_PAWR_NUM_RSP_SLOTS; + + rc = ble_gap_periodic_adv_configure(instance, &pparams); + assert(rc == 0); + + /* start periodic advertising */ +#if MYNEWT_VAL(BLE_PERIODIC_ADV_ENH) + eparams.include_adi = 1; + rc = ble_gap_periodic_adv_start(instance, &eparams); +#else + rc = ble_gap_periodic_adv_start(instance); +#endif + assert (rc == 0); + + /* start advertising */ + rc = ble_gap_ext_adv_start(instance, 0, 0); + assert (rc == 0); + + ESP_LOGI(TAG, "instance %u started (periodic)\n", instance); +} + +static void +on_reset(int reason) +{ + ESP_LOGE(TAG, "Resetting state; reason=%d\n", reason); +} + +static void +on_sync(void) +{ + /* Begin advertising. */ + start_periodic_adv(); +} + +void pawr_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +void +app_main(void) +{ + esp_err_t ret; + + /* Initialize NVS — it is used to store PHY calibration data */ + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to init nimble %d ", ret); + return; + } + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = on_reset; + ble_hs_cfg.sync_cb = on_sync; + + nimble_port_freertos_init(pawr_host_task); +} diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/sdkconfig.defaults b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/sdkconfig.defaults new file mode 100644 index 0000000000..20b00aad69 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/sdkconfig.defaults @@ -0,0 +1,35 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_EXT_ADV=y +CONFIG_BT_NIMBLE_PERIODIC_ADV_WITH_RESPONSES=y +CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1 +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=n + +CONFIG_BT_CONTROLLER_DISABLED=y + +# +# Host-controller Transport +# +CONFIG_BT_NIMBLE_TRANSPORT_UART_PORT=1 +CONFIG_UART_BAUDRATE_115200=y +CONFIG_BT_NIMBLE_UART_TX_PIN=20 +CONFIG_BT_NIMBLE_UART_RX_PIN=21 +CONFIG_UART_HW_FLOWCTRL_CTS_RTS=n +# CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=22 +# CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 +# end of Host-controller Transport +# end of NimBLE Options + +# C6 Nordic +# TX: 20 ---- RX +# RX: 21 ---- TX +# RTS: 22 ---- CTS +# CTS: 23 ---- RTS diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/tutorial/BLE_periodic_adv_walkthrough.md b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/tutorial/BLE_periodic_adv_walkthrough.md new file mode 100644 index 0000000000..a7c8dbfc46 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_adv_conn/tutorial/BLE_periodic_adv_walkthrough.md @@ -0,0 +1,320 @@ +# BLE Periodic Advertisement Example Walkthrough + +## Introduction + +In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address. + +## Includes + +This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are: + +```c +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#include "services/gap/ble_svc_gap.h" +#include "periodic_adv.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "patterns.h" +``` +These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“periodic_adv.h”` which expose the BLE APIs required to implement this example. + +* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack. +* `nimble_port_freertos.h`: Initializes and enables nimble host task. +* `ble_hs.h`: Defines the functionalities to handle the host event +* `ble_svc_gap.h`:Defines the macros for device name, and device appearance and declares the function to set them. +* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined. + +## Main Entry Point + +The program’s entry point is the app_main() function: +```c +void +app_main(void) +{ + int rc; + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(tag, "Failed to init nimble %d ", ret); + return; + } + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = periodic_adv_on_reset; + ble_hs_cfg.sync_cb = periodic_adv_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble_periodic_adv"); + assert(rc == 0); + + /* XXX Need to have a template for store */ + ble_store_config_init(); + + nimble_port_freertos_init(periodic_adv_host_task); +} +``` +The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations. +```c +esp_err_t ret = nvs_flash_init(); +if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); +} +ESP_ERROR_CHECK( ret ); +``` + +## BT Controller and Stack Initialization + +The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions: + +```c +esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +ret = esp_bt_controller_init(&config_opts); +``` +Next, the controller is enabled in BLE Mode. + +```c +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); +``` +The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode. + +There are four Bluetooth modes supported: + +1. `ESP_BT_MODE_IDLE`: Bluetooth not running +2. `ESP_BT_MODE_BLE`: BLE mode +3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode +4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic) + +After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`: + +```c +esp_err_t esp_nimble_init(void) +{ + +#if !SOC_ESP_NIMBLE_CONTROLLER + /* Initialize the function pointers for OS porting */ + npl_freertos_funcs_init(); + + npl_freertos_mempool_init(); + + if(esp_nimble_hci_init() != ESP_OK) { + ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n"); + return ESP_FAIL; + } + + /* Initialize default event queue */ + ble_npl_eventq_init(&g_eventq_dflt); + + os_msys_init(); + + void ble_store_ram_init(void); + /* XXX Need to have a template for store */ + ble_store_ram_init(); +#endif + + /* Initialize the host */ + ble_hs_init(); + return ESP_OK; +} +``` + +The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status + +```c +ble_hs_cfg.reset_cb = periodic_adv_on_reset; +ble_hs_cfg.sync_cb = periodic_adv_on_sync; +ble_hs_cfg.store_status_cb = ble_store_util_status_rr; +``` + +The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function. +```c +rc = ble_svc_gap_device_name_set("nimble_periodic_adv"); +``` +main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material. +```c +/* XXX Need to have a template for store */ + ble_store_config_init(); +``` + +The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`. + +```c +nimble_port_freertos_init(periodic_adv_host_task); +``` +`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but to handle the default queue, it is easier to create a separate task. + +## Generation of non-resolvable private address + +In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`. + +```c +#if CONFIG_EXAMPLE_RANDOM_ADDR +static void +periodic_adv_set_addr(void) +{ + ble_addr_t addr; + int rc; + + /* generate new non-resolvable private address */ + rc = ble_hs_id_gen_rnd(0, &addr); + assert(rc == 0); + + /* set generated address */ + rc = ble_hs_id_set_rnd(addr.val); + + assert(rc == 0); +} +#endif +``` + +## Periodic Advertisement + +Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively. +Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value. + +## Need of Extended Advertisement in Periodic Advertisement + +Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement. + + +Below is the implementation to start periodic advertisement. + +```c +static void +start_periodic_adv(void) +{ + int rc; + struct ble_gap_periodic_adv_params pparams; + struct ble_gap_ext_adv_params params; + struct ble_hs_adv_fields adv_fields; + struct os_mbuf *data; + uint8_t instance = 1; + ble_addr_t addr; + + /* set random (NRPA) address for instance */ + rc = ble_hs_id_gen_rnd(1, &addr); + assert (rc == 0); + + MODLOG_DFLT(INFO, "Device Address: "); + print_addr(addr.val); + MODLOG_DFLT(INFO, "\n"); + + /* For periodic we use instance with non-connectable advertising */ + memset (¶ms, 0, sizeof(params)); + + /* advertise using random addr */ + params.own_addr_type = BLE_OWN_ADDR_RANDOM; + params.primary_phy = BLE_HCI_LE_PHY_1M; + params.secondary_phy = BLE_HCI_LE_PHY_2M; + params.sid = 2; + + /* configure instance 1 */ + rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, NULL, NULL); + assert (rc == 0); + + rc = ble_gap_ext_adv_set_addr(instance, &addr ); + assert (rc == 0); + + memset(&adv_fields, 0, sizeof(adv_fields)); + adv_fields.name = (const uint8_t *)"Periodic ADV"; + adv_fields.name_len = strlen((char *)adv_fields.name); + + /* Default to legacy PDUs size, mbuf chain will be increased if needed + */ + data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0); + assert(data); + + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data); + assert(rc == 0); + + rc = ble_gap_ext_adv_set_data(instance, data); + assert(rc == 0); + + /* configure periodic advertising */ + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; + pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); + pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); + + rc = ble_gap_periodic_adv_configure(instance, &pparams); + assert(rc == 0); + + data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0); + assert(data); + + rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data)); + assert(rc == 0); + rc = ble_gap_periodic_adv_set_data(instance, data); + assert (rc == 0); + + /* start periodic advertising */ + assert (rc == 0 rc = ble_gap_periodic_adv_start(instance); +); + + /* start advertising */ + rc = ble_gap_ext_adv_start(instance, 0, 0); + assert (rc == 0); + + MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance); +} +``` + +The periodic advertisement uses a non-connectable advertising mode. `memset (¶ms, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0. + +## Parameter Configuration + +The below snippets represent the parameter configuration for extended and periodic advertisement. + +### For Extended Advertisement + +```c + params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random + params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M + params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M + params.sid = 2; // Advertising set Id is assigned with value 2. +``` + +### For Periodic Advertisement + +```c + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU + pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms + pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms +``` + +Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines. + +```c + struct ble_hci_le_set_periodic_adv_enable_cp cmd; + cmd.enable = 0x01; + cmd.adv_handle = instance; +``` + +Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively. + +Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration. + +max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit. + +## Conclusion + +This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough. + +1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time. +2. Periodic advertisement uses NRPA (Non Resolvable private address). It is a randomly generated address that changes periodically to prevent long-term tracking of a device. +3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time. \ No newline at end of file diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/CMakeLists.txt b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/CMakeLists.txt new file mode 100644 index 0000000000..a46859beb6 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_pawr_sync_conn) diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/README.md b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/README.md new file mode 100644 index 0000000000..fac8917b06 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/README.md @@ -0,0 +1,75 @@ +| Supported Targets | ESP32 | ESP32S2 | ESP32-C3 | ESP32-S3 | ESP32-C2 | +| ----------------- | ----- | ------- | -------- | -------- | -------- | + +# BLE Periodic Advertiser Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example starts periodic advertising with non resolvable private address. + +It uses Bluetooth controller and NimBLE stack based BLE host. + +This example aims at understanding periodic advertisement and related NimBLE APIs. + + +To test this demo, any BLE Periodic Sync app can be used. + + +Note : + +* Make sure to run `python -m pip install --user -r $IDF_PATH/requirements.txt -r $IDF_PATH/tools/ble/requirements.txt` to install the dependency packages needed. +* Currently this Python utility is only supported on Linux (BLE communication is via BLuez + DBus). + +## How to Use Example + +Before project configuration and build, be sure to set the correct chip target using: + +```bash +idf.py set-target +``` + +### Configure the project + +Open the project configuration menu: + +```bash +idf.py menuconfig +``` + +In the `Example Configuration` menu: + +* Select I/O capabilities of device from `Example Configuration --> I/O Capability`, default is `Just_works`. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +There is this console output when periodic_adv is started: + +``` +I (313) BTDM_INIT: BT controller compile version [2ee0168] +I (313) phy_init: phy_version 912,d001756,Jun 2 2022,16:28:07 +I (353) system_api: Base MAC address is not set +I (353) system_api: read default base MAC address from EFUSE +I (353) BTDM_INIT: Bluetooth MAC: 84:f7:03:08:14:8e + +I (363) NimBLE_BLE_PERIODIC_ADV: BLE Host Task Started +I (373) NimBLE: Device Address: +I (373) NimBLE: d0:42:3a:95:84:05 +I (373) NimBLE: + +I (383) NimBLE: instance 1 started (periodic) +``` + +## Note +* Periodic sync transfer is not implemented for now. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/CMakeLists.txt b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/CMakeLists.txt new file mode 100644 index 0000000000..023dd5e462 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(srcs "main.c") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/Kconfig.projbuild b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/Kconfig.projbuild new file mode 100644 index 0000000000..c7b32ae16d --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/Kconfig.projbuild @@ -0,0 +1,27 @@ +menu "Example Configuration" + + config EXAMPLE_EXTENDED_ADV + bool + depends on SOC_BLE_50_SUPPORTED + default y if SOC_ESP_NIMBLE_CONTROLLER + select BT_NIMBLE_EXT_ADV + prompt "Enable Extended Adv" + help + Use this option to enable extended advertising in the example. + If you disable this option, ensure config BT_NIMBLE_EXT_ADV is + also disabled from Nimble stack menuconfig. + + config EXAMPLE_RANDOM_ADDR + bool + prompt "Advertise RANDOM Address" + help + Use this option to advertise a random address instead of public address + + config EXAMPLE_PERIODIC_ADV_ENH + bool + prompt "Enable Periodic Adv Enhancements" + depends on SOC_BLE_50_SUPPORTED && SOC_BLE_PERIODIC_ADV_ENH_SUPPORTED + select BT_NIMBLE_PERIODIC_ADV_ENH + help + Use this option to enable periodic advertising enhancements +endmenu diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/idf_component.yml b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/idf_component.yml new file mode 100644 index 0000000000..d6e735fe77 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + nimble_peripheral_utils: + path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/main.c b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/main.c new file mode 100644 index 0000000000..badce40af7 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/main/main.c @@ -0,0 +1,292 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" + +#define TAG "NimBLE_BLE_PAwR" +#define TARGET_NAME "Nimble_PAwR" +#define BLE_PAWR_RSP_DATA_LEN (20) +static uint8_t sub_data_pattern[BLE_PAWR_RSP_DATA_LEN] = {0}; + +static int create_periodic_sync(struct ble_gap_ext_disc_desc *disc); +static void start_scan(void); + +static struct ble_hs_adv_fields fields; +static bool synced = false; +uint8_t device_addr[6]; + +static struct ble_gap_conn_desc desc; +char * +addr_str(const void *addr) +{ + static char buf[6 * 2 + 5 + 1]; + const uint8_t *u8p; + + u8p = addr; + sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x", + u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); + + return buf; +} + +void +print_conn_desc(const struct ble_gap_conn_desc *desc) +{ + ESP_LOGI(TAG,"handle=%d our_ota_addr_type=%d our_ota_addr=%s ", + desc->conn_handle, desc->our_ota_addr.type, + addr_str(desc->our_ota_addr.val)); + ESP_LOGI(TAG, "our_id_addr_type=%d our_id_addr=%s ", + desc->our_id_addr.type, addr_str(desc->our_id_addr.val)); + ESP_LOGI(TAG, "peer_ota_addr_type=%d peer_ota_addr=%s ", + desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val)); + ESP_LOGI(TAG, "peer_id_addr_type=%d peer_id_addr=%s ", + desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val)); + ESP_LOGI(TAG, "conn_itvl=%d conn_latency=%d supervision_timeout=%d " + "encrypted=%d authenticated=%d bonded=%d\n", + desc->conn_itvl, desc->conn_latency, + desc->supervision_timeout, + desc->sec_state.encrypted, + desc->sec_state.authenticated, + desc->sec_state.bonded); +} +static int +gap_event_cb(struct ble_gap_event *event, void *arg) +{ + int rc; + uint8_t *addr; + struct ble_gap_ext_disc_desc *disc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + ESP_LOGI(TAG, "Connection established, conn_handle = 0x%0x, sync handle= 0x%02x, status = 0x%0x\n",event->connect.conn_handle, event->connect.sync_handle, event->connect.status); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + if(rc == 0){ + print_conn_desc(&desc); + } + else{ + ESP_LOGE(TAG,"Failed to find Conn Information"); + } + return 0; + case BLE_GAP_EVENT_EXT_DISC: + disc = &event->ext_disc; + addr = disc->addr.val; + ESP_LOGI(TAG, "[Disc advertiser] addr %02x:%02x:%02x:%02x:%02x:%02x, props: 0x%x, rssi:%d", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0], disc->props, disc->rssi); + if (synced) { + return 0; + } + + rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data); + if (rc != 0) { + ESP_LOGE(TAG, "Failed to parse adv data, rc = %d", rc); + return 0; + } + + if (disc->periodic_adv_itvl && fields.name_len && !memcmp(fields.name, TARGET_NAME, strlen(TARGET_NAME))) { + create_periodic_sync(disc); + } + return 0; + + case BLE_GAP_EVENT_PERIODIC_REPORT: + if (event->periodic_report.event_counter % 10 == 0) { + // print every 10th event + ESP_LOGI(TAG, "[Periodic Adv Report] handle:%d, rssi:%d, data status:0x%x", + event->periodic_report.sync_handle, event->periodic_report.rssi, + event->periodic_report.data_status); + ESP_LOGI(TAG, "[Periodic Adv Report] event_counter(%d), subevent(%d)", + event->periodic_report.event_counter, event->periodic_report.subevent); + } + + struct ble_gap_periodic_adv_response_params param = { + .request_event = event->periodic_report.event_counter, + .request_subevent = event->periodic_report.subevent, + .response_subevent = event->periodic_report.subevent, + .response_slot = 0 + }; + + struct os_mbuf *data = os_msys_get_pkthdr(BLE_PAWR_RSP_DATA_LEN, 0); + if (!data) { + ESP_LOGE(TAG, "No memory"); + return 0; + } + // create a special data for checking manually in ADV side + sub_data_pattern[0] = event->periodic_report.data[0]; + rc = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, device_addr, NULL); + memset(sub_data_pattern + 1, event->periodic_report.subevent, BLE_PAWR_RSP_DATA_LEN - 1); + memcpy(sub_data_pattern + 1,device_addr,BLE_DEV_ADDR_LEN); + os_mbuf_append(data, sub_data_pattern, BLE_PAWR_RSP_DATA_LEN); + + rc = ble_gap_periodic_adv_set_response_data(event->periodic_report.sync_handle, ¶m, data); + if (rc) { + ESP_LOGE(TAG, "Set response data failed, subev(%x), rsp_slot(%d), rc(0x%x)", + sub_data_pattern[0], event->periodic_report.subevent, rc); + } + os_mbuf_free_chain(data); + + return 0; + + case BLE_GAP_EVENT_PERIODIC_SYNC_LOST: + ESP_LOGE(TAG, "[Periodic Sync Lost] handle:%d, Reason = 0x%x", + event->periodic_sync_lost.sync_handle, event->periodic_sync_lost.reason); + synced = false; + start_scan(); + return 0; + + case BLE_GAP_EVENT_PERIODIC_SYNC: + if (!event->periodic_sync.status) { + ESP_LOGI(TAG, "[Periodic Sync Established] sync handle:%d, num_subevents:0x%x", + event->periodic_sync.sync_handle, event->periodic_sync.num_subevents); + ESP_LOGI(TAG, "subevent_interval:0x%x, slot_delay:0x%x,slot_spacing:0x%x", + event->periodic_sync.subevent_interval, + event->periodic_sync.response_slot_delay, + event->periodic_sync.response_slot_spacing); + ble_gap_disc_cancel(); + + // choose subevents in range 0 to (num_subevents - 1) + uint8_t subevents[] = {0, 1, 2, 3, 4}; + int result = ble_gap_periodic_adv_sync_subev(event->periodic_sync.sync_handle, 0, sizeof(subevents), subevents); + if (result == ESP_OK) { + ESP_LOGI(TAG, "[Subevent Sync OK] sync handle:%d, sync_subevents:%d\n", event->periodic_sync.sync_handle, sizeof(subevents)); + } else { + ESP_LOGE(TAG, "Failed to sync subevents, rc = 0x%x", result); + } + } else { + ESP_LOGE(TAG, "Periodic Sync Error, status = %d", event->periodic_sync.status); + synced = false; + start_scan(); + } + return 0; + + default: + return 0; + } +} + +static int +create_periodic_sync(struct ble_gap_ext_disc_desc *disc) +{ + int rc; + struct ble_gap_periodic_sync_params params; + + params.skip = 0; + params.sync_timeout = 4000; + params.reports_disabled = 0; + +#if CONFIG_EXAMPLE_PERIODIC_ADV_ENH + /* This way the periodic advertising reports will not be + delivered to host unless the advertising data is changed + or the Data-Id is updated by the advertiser */ + params.filter_duplicates = 1; +#endif + + rc = ble_gap_periodic_adv_sync_create(&disc->addr, disc->sid, ¶ms, gap_event_cb, NULL); + if (!rc) { + synced = true; + ESP_LOGI(TAG, "Create sync"); + } else { + ESP_LOGE(TAG, "Failed to create sync, rc = %d", rc); + } + + return rc; +} + +static void +start_scan(void) +{ + int rc; + struct ble_gap_ext_disc_params disc_params; + + /* Perform a passive scan. I.e., don't send follow-up scan requests to + * each advertiser. + */ + disc_params.itvl = BLE_GAP_SCAN_ITVL_MS(600); + disc_params.window = BLE_GAP_SCAN_ITVL_MS(300); + disc_params.passive = 1; + + /* Tell the controller to filter duplicates; we don't want to process + * repeated advertisements from the same device. + */ + rc = ble_gap_ext_disc(BLE_OWN_ADDR_PUBLIC, 0, 0, 1, 0, 0, NULL, &disc_params, + gap_event_cb, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "Error initiating GAP discovery procedure; rc=%d\n", rc); + } +} + +static void +on_reset(int reason) +{ + ESP_LOGE(TAG, "Resetting state; reason=%d\n", reason); +} + +/* Cnnot find `ble_single_xxxx()`, workaround */ +// static void +// on_sync(void) +// { +// int ble_single_env_init(void); +// int ble_single_init(void); + +// int rc; + +// rc = ble_single_env_init(); +// assert(!rc); +// rc = ble_single_init(); +// assert(!rc); + +// start_scan(); +// } + +static void +on_sync(void) +{ + int rc; + + /* Make sure we have proper identity address set (public preferred) */ + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + start_scan(); +} + +void pawr_host_task(void *param) +{ + ESP_LOGI(TAG, "BLE Host Task Started"); + /* This function will return only when nimble_port_stop() is executed */ + nimble_port_run(); + + nimble_port_freertos_deinit(); +} + +void +app_main(void) +{ + esp_err_t ret; + + /* Initialize NVS — it is used to store PHY calibration data */ + ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to init nimble %d ", ret); + return; + } + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = on_reset; + ble_hs_cfg.sync_cb = on_sync; + + nimble_port_freertos_init(pawr_host_task); +} diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/sdkconfig.defaults b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/sdkconfig.defaults new file mode 100644 index 0000000000..83db34e62f --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/sdkconfig.defaults @@ -0,0 +1,35 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_EXT_ADV=n +CONFIG_BT_NIMBLE_PERIODIC_ADV_WITH_RESPONSES=y +CONFIG_BT_NIMBLE_MAX_PERIODIC_SYNCS=1 +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=y + +CONFIG_BT_CONTROLLER_DISABLED=y + +# +# Host-controller Transport +# +CONFIG_BT_NIMBLE_TRANSPORT_UART_PORT=1 +CONFIG_UART_BAUDRATE_115200=y +CONFIG_BT_NIMBLE_UART_TX_PIN=20 +CONFIG_BT_NIMBLE_UART_RX_PIN=21 +CONFIG_UART_HW_FLOWCTRL_CTS_RTS=n +# CONFIG_BT_NIMBLE_HCI_UART_RTS_PIN=22 +# CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23 +# end of Host-controller Transport +# end of NimBLE Options + +# C6 Nordic +# TX: 20 ---- RX +# RX: 21 ---- TX +# RTS: 22 ---- CTS +# CTS: 23 ---- RTS diff --git a/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/tutorial/BLE_periodic_adv_walkthrough.md b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/tutorial/BLE_periodic_adv_walkthrough.md new file mode 100644 index 0000000000..a7c8dbfc46 --- /dev/null +++ b/examples/bluetooth/nimble/ble_pawr_adv_conn/ble_pawr_sync_conn/tutorial/BLE_periodic_adv_walkthrough.md @@ -0,0 +1,320 @@ +# BLE Periodic Advertisement Example Walkthrough + +## Introduction + +In this tutorial, the ble_periodic_adv example code for the espressif chipsets with BLE5.0 support is reviewed. This example aims at understanding periodic advertisements and related NimBLE APIs.This code implements the periodic advertisement functionality along with extended advertisement by generating a non-resolvable private address. + +## Includes + +This example is located in the examples folder of the ESP-IDF under the [ble_periodic_adv/main](../main). The [main.c](../main/main.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [main.c](../main/main.c) are: + +```c +#include "esp_log.h" +#include "nvs_flash.h" +/* BLE */ +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/util/util.h" +#include "console/console.h" +#include "services/gap/ble_svc_gap.h" +#include "periodic_adv.h" +#include "host/ble_gap.h" +#include "host/ble_hs_adv.h" +#include "patterns.h" +``` +These `includes` are required for the FreeRTOS and underlying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“nimble_port.h”`, `“nimble_port_freertos.h”`, `"ble_hs.h"` and `“ble_svc_gap.h”`, `“periodic_adv.h”` which expose the BLE APIs required to implement this example. + +* `nimble_port.h`: Includes the declaration of functions required for the initialization of the nimble stack. +* `nimble_port_freertos.h`: Initializes and enables nimble host task. +* `ble_hs.h`: Defines the functionalities to handle the host event +* `ble_svc_gap.h`:Defines the macros for device name, and device appearance and declares the function to set them. +* `periodic_adv.h`:It includes the code containing forward declarations of 2 structs `ble_hs_cfg` , and `ble_gatt_register_ctxt` based on weather macro `H_BLE_PERIODIC_ADV_` is defined. + +## Main Entry Point + +The program’s entry point is the app_main() function: +```c +void +app_main(void) +{ + int rc; + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + ret = nimble_port_init(); + if (ret != ESP_OK) { + ESP_LOGE(tag, "Failed to init nimble %d ", ret); + return; + } + + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = periodic_adv_on_reset; + ble_hs_cfg.sync_cb = periodic_adv_on_sync; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + /* Set the default device name. */ + rc = ble_svc_gap_device_name_set("nimble_periodic_adv"); + assert(rc == 0); + + /* XXX Need to have a template for store */ + ble_store_config_init(); + + nimble_port_freertos_init(periodic_adv_host_task); +} +``` +The main function starts by initializing the non-volatile storage library. This library allows us to save the key-value pairs in flash memory.`nvs_flash_init()` stores the PHY calibration data. In a Bluetooth Low Energy (BLE) device, cryptographic keys used for encryption and authentication are often stored in Non-Volatile Storage (NVS).BLE stores the peer keys, CCCD keys, peer records, etc on NVS. By storing these keys in NVS, the BLE device can quickly retrieve them when needed, without the need for time-consuming key generations. +```c +esp_err_t ret = nvs_flash_init(); +if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); +} +ESP_ERROR_CHECK( ret ); +``` + +## BT Controller and Stack Initialization + +The main function calls `nimble_port_init()` to initialize BT Controller and nimble stack. This function initializes the BT controller by first creating its configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. It implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL), and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority, and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` and `esp_bt_controller_enable()` functions: + +```c +esp_bt_controller_config_t config_opts = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +ret = esp_bt_controller_init(&config_opts); +``` +Next, the controller is enabled in BLE Mode. + +```c +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); +``` +The controller should be enabled in `ESP_BT_MODE_BLE` if you want to use the BLE mode. + +There are four Bluetooth modes supported: + +1. `ESP_BT_MODE_IDLE`: Bluetooth not running +2. `ESP_BT_MODE_BLE`: BLE mode +3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode +4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic) + +After the initialization of the BT controller, the nimble stack, which includes the common definitions and APIs for BLE, is initialized by using `esp_nimble_init()`: + +```c +esp_err_t esp_nimble_init(void) +{ + +#if !SOC_ESP_NIMBLE_CONTROLLER + /* Initialize the function pointers for OS porting */ + npl_freertos_funcs_init(); + + npl_freertos_mempool_init(); + + if(esp_nimble_hci_init() != ESP_OK) { + ESP_LOGE(NIMBLE_PORT_LOG_TAG, "hci inits failed\n"); + return ESP_FAIL; + } + + /* Initialize default event queue */ + ble_npl_eventq_init(&g_eventq_dflt); + + os_msys_init(); + + void ble_store_ram_init(void); + /* XXX Need to have a template for store */ + ble_store_ram_init(); +#endif + + /* Initialize the host */ + ble_hs_init(); + return ESP_OK; +} +``` + +The host is configured by setting up the callbacks for Stack-reset, Stack-sync, and Storage status + +```c +ble_hs_cfg.reset_cb = periodic_adv_on_reset; +ble_hs_cfg.sync_cb = periodic_adv_on_sync; +ble_hs_cfg.store_status_cb = ble_store_util_status_rr; +``` + +The main function calls `ble_svc_gap_device_name_set()` to set the default device name. 'blecent_phy' is passed as the default device name to this function. +```c +rc = ble_svc_gap_device_name_set("nimble_periodic_adv"); +``` +main function calls `ble_store_config_init()` to configure the host by setting up the storage callbacks which handle the read, write, and deletion of security material. +```c +/* XXX Need to have a template for store */ + ble_store_config_init(); +``` + +The main function ends by creating a task where nimble will run using `nimble_port_freertos_init()`. This enables the nimble stack by using `esp_nimble_enable()`. + +```c +nimble_port_freertos_init(periodic_adv_host_task); +``` +`esp_nimble_enable()` create a task where the nimble host will run. It is not strictly necessary to have a separate task for the nimble host, but to handle the default queue, it is easier to create a separate task. + +## Generation of non-resolvable private address + +In Bluetooth Low Energy (BLE), a non-resolvable private address is a type of Bluetooth device address that is used for privacy purposes. It is a randomly generated address that changes periodically to prevent long-term tracking of a device. The API call to `ble_hs_id_gen_rnd()` is responsible for generating a non-resolvable private address. NRPA is a 48-bit address that is stored in `addr.val`. + +```c +#if CONFIG_EXAMPLE_RANDOM_ADDR +static void +periodic_adv_set_addr(void) +{ + ble_addr_t addr; + int rc; + + /* generate new non-resolvable private address */ + rc = ble_hs_id_gen_rnd(0, &addr); + assert(rc == 0); + + /* set generated address */ + rc = ble_hs_id_set_rnd(addr.val); + + assert(rc == 0); +} +#endif +``` + +## Periodic Advertisement + +Periodic advertisement start by creating instances of structures `ble_gap_periodic_adv_params`, `ble_gap_ext_adv_params`, `ble_hs_adv_fields`, and `os_mbuf`. Advertising parameters such as connecting modes, advertising intervals, peer address, advertising-filter policy, etc are defined in these structures for periodic and extended advertisements. `pparams` and `params` instances have parameters for periodic advertisement and extended advertisement respectively. +Bluetooth device address is given by the structure ble_aadr_t which contains the fields for address type and address value. + +## Need of Extended Advertisement in Periodic Advertisement + +Non-connectable and non-scannable advertising events containing synchronization information about a periodic advertising train are necessary for the scanner device to sync with the periodic advertising train. The periodic advertising will utilize the same physical layer (PHY) as the auxiliary packet, which is part of the extended advertisement. + + +Below is the implementation to start periodic advertisement. + +```c +static void +start_periodic_adv(void) +{ + int rc; + struct ble_gap_periodic_adv_params pparams; + struct ble_gap_ext_adv_params params; + struct ble_hs_adv_fields adv_fields; + struct os_mbuf *data; + uint8_t instance = 1; + ble_addr_t addr; + + /* set random (NRPA) address for instance */ + rc = ble_hs_id_gen_rnd(1, &addr); + assert (rc == 0); + + MODLOG_DFLT(INFO, "Device Address: "); + print_addr(addr.val); + MODLOG_DFLT(INFO, "\n"); + + /* For periodic we use instance with non-connectable advertising */ + memset (¶ms, 0, sizeof(params)); + + /* advertise using random addr */ + params.own_addr_type = BLE_OWN_ADDR_RANDOM; + params.primary_phy = BLE_HCI_LE_PHY_1M; + params.secondary_phy = BLE_HCI_LE_PHY_2M; + params.sid = 2; + + /* configure instance 1 */ + rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL, NULL, NULL); + assert (rc == 0); + + rc = ble_gap_ext_adv_set_addr(instance, &addr ); + assert (rc == 0); + + memset(&adv_fields, 0, sizeof(adv_fields)); + adv_fields.name = (const uint8_t *)"Periodic ADV"; + adv_fields.name_len = strlen((char *)adv_fields.name); + + /* Default to legacy PDUs size, mbuf chain will be increased if needed + */ + data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0); + assert(data); + + rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data); + assert(rc == 0); + + rc = ble_gap_ext_adv_set_data(instance, data); + assert(rc == 0); + + /* configure periodic advertising */ + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; + pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); + pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); + + rc = ble_gap_periodic_adv_configure(instance, &pparams); + assert(rc == 0); + + data = os_msys_get_pkthdr(sizeof(periodic_adv_raw_data), 0); + assert(data); + + rc = os_mbuf_append(data, periodic_adv_raw_data, sizeof(periodic_adv_raw_data)); + assert(rc == 0); + rc = ble_gap_periodic_adv_set_data(instance, data); + assert (rc == 0); + + /* start periodic advertising */ + assert (rc == 0 rc = ble_gap_periodic_adv_start(instance); +); + + /* start advertising */ + rc = ble_gap_ext_adv_start(instance, 0, 0); + assert (rc == 0); + + MODLOG_DFLT(INFO, "instance %u started (periodic)\n", instance); +} +``` + +The periodic advertisement uses a non-connectable advertising mode. `memset (¶ms, 0, sizeof(params))` initialises params to 0. This also sets `params.connectable` to 0. + +## Parameter Configuration + +The below snippets represent the parameter configuration for extended and periodic advertisement. + +### For Extended Advertisement + +```c + params.own_addr_type = BLE_OWN_ADDR_RANDOM; //Own address type is set to Random + params.primary_phy = BLE_HCI_LE_PHY_1M; // Primary advertising PHY is set to 1M + params.secondary_phy = BLE_HCI_LE_PHY_2M; // Secondary advertising PHY is set to 2M + params.sid = 2; // Advertising set Id is assigned with value 2. +``` + +### For Periodic Advertisement + +```c + memset(&pparams, 0, sizeof(pparams)); + pparams.include_tx_power = 0; // Indicates that TX power is not included in advertising PDU + pparams.itvl_min = BLE_GAP_ADV_ITVL_MS(120); // Minimum advertising interval of 240ms + pparams.itvl_max = BLE_GAP_ADV_ITVL_MS(240); //Maximum advertising interval of 480ms +``` + +Periodic advertisement is started for a particular advertisement instance by calling the API `ble_gap_periodic_adv_start(instance)`. This function takes instance-id as an input parameter. It defines the hci command by initializing the command parameters which are represented in the following lines. + +```c + struct ble_hci_le_set_periodic_adv_enable_cp cmd; + cmd.enable = 0x01; + cmd.adv_handle = instance; +``` + +Extended advertising is invoked for a particular instance using the API call `ble_gap_ext_adv_start(instance, 0, 0)`.Instance-id, duration, and max_events are input parameters for this API call respectively. + +Duration represents the time for which the adverteiment will take place. Upon expiration, the advertising procedure ends, and the BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no expiration. + +max_events Number of advertising events that should be sent before advertising ends and a BLE_GAP_EVENT_ADV_COMPLETE event is reported.0 value is used for no limit. + +## Conclusion + +This Walkthrough covers the code explanation of the BLE_PERIODIC_ADV. The following points are concluded through this walkthrough. + +1. Periodic advertising allows the scanner to sync with the advertiser so the scanner and advertiser would wake up at the same time. +2. Periodic advertisement uses NRPA (Non Resolvable private address). It is a randomly generated address that changes periodically to prevent long-term tracking of a device. +3. Extended advertising is used to indicate to the scanner that the advertiser is utilizing periodic advertising. Therefore, periodic advertising is started before extended advertising so that the scanner and advertiser can synchronize their actions and operate at the same time. \ No newline at end of file From 444cb8778ad9f7d72061c8535af5f8f51e054c26 Mon Sep 17 00:00:00 2001 From: Sumeet Singh Date: Tue, 19 Nov 2024 14:27:45 +0530 Subject: [PATCH 21/21] feat(nimble): backport fixes for BLE 5.4 PTS Related Features and Fixes --- components/bt/host/nimble/nimble | 2 +- components/bt/host/nimble/port/include/esp_nimble_cfg.h | 2 +- examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c | 1 + .../nimble/ble_enc_adv_data/enc_adv_data_prph/main/gatt_svr.c | 1 + examples/bluetooth/nimble/ble_multi_adv/main/gatt_svr.c | 1 + examples/bluetooth/nimble/bleprph/main/gatt_svr.c | 1 + examples/bluetooth/nimble/power_save/main/gatt_svr.c | 1 + 7 files changed, 7 insertions(+), 2 deletions(-) diff --git a/components/bt/host/nimble/nimble b/components/bt/host/nimble/nimble index dfdcf0ba53..abf630be42 160000 --- a/components/bt/host/nimble/nimble +++ b/components/bt/host/nimble/nimble @@ -1 +1 @@ -Subproject commit dfdcf0ba53c908d5a2f0d0f5848f6acf4799930f +Subproject commit abf630be427e40426af172bf0eabd702697531b9 diff --git a/components/bt/host/nimble/port/include/esp_nimble_cfg.h b/components/bt/host/nimble/port/include/esp_nimble_cfg.h index 2eaae6ee8a..d2df99b4b5 100644 --- a/components/bt/host/nimble/port/include/esp_nimble_cfg.h +++ b/components/bt/host/nimble/port/include/esp_nimble_cfg.h @@ -964,7 +964,7 @@ #endif #ifndef MYNEWT_VAL_BLE_STORE_MAX_CSFCS -#define MYNEWT_VAL_BLE_STORE_MAX_CSFCS (CONFIG_BT_NIMBLE_MAX_CONNECTIONS) +#define MYNEWT_VAL_BLE_STORE_MAX_CSFCS CONFIG_BT_NIMBLE_MAX_BONDS #endif #ifdef CONFIG_BT_NIMBLE_MAX_EADS diff --git a/examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c b/examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c index f6afece49c..d03ae5a013 100644 --- a/examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_dynamic_service/main/gatt_svr.c @@ -60,6 +60,7 @@ const struct ble_gatt_svc_def gatt_svr_svcs[] = { 0, /* No more descriptors in this characteristic */ } }, + .cpfd = NULL, }, { 0, /* No more characteristics in this service. */ } diff --git a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/gatt_svr.c b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/gatt_svr.c index 1b1bdd06dd..2c5711210d 100644 --- a/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_enc_adv_data/enc_adv_data_prph/main/gatt_svr.c @@ -70,6 +70,7 @@ static const struct ble_gatt_svc_def gatt_svr_svcs[] = { 0, /* No more descriptors in this characteristic */ } }, + .cpfd = NULL, }, { 0, /* No more characteristics in this service. */ } diff --git a/examples/bluetooth/nimble/ble_multi_adv/main/gatt_svr.c b/examples/bluetooth/nimble/ble_multi_adv/main/gatt_svr.c index a142dc9091..ea32f61725 100644 --- a/examples/bluetooth/nimble/ble_multi_adv/main/gatt_svr.c +++ b/examples/bluetooth/nimble/ble_multi_adv/main/gatt_svr.c @@ -55,6 +55,7 @@ static const struct ble_gatt_svc_def gatt_svr_svcs[] = { 0, /* No more descriptors in this characteristic */ } }, + .cpfd = NULL, }, { 0, /* No more characteristics in this service. */ } diff --git a/examples/bluetooth/nimble/bleprph/main/gatt_svr.c b/examples/bluetooth/nimble/bleprph/main/gatt_svr.c index d48a35f0ab..75f488da57 100644 --- a/examples/bluetooth/nimble/bleprph/main/gatt_svr.c +++ b/examples/bluetooth/nimble/bleprph/main/gatt_svr.c @@ -83,6 +83,7 @@ static const struct ble_gatt_svc_def gatt_svr_svcs[] = { 0, /* No more descriptors in this characteristic */ } }, + .cpfd = NULL, }, { 0, /* No more characteristics in this service. */ } diff --git a/examples/bluetooth/nimble/power_save/main/gatt_svr.c b/examples/bluetooth/nimble/power_save/main/gatt_svr.c index f59de24e10..402df8bcbc 100644 --- a/examples/bluetooth/nimble/power_save/main/gatt_svr.c +++ b/examples/bluetooth/nimble/power_save/main/gatt_svr.c @@ -70,6 +70,7 @@ static const struct ble_gatt_svc_def gatt_svr_svcs[] = { 0, /* No more descriptors in this characteristic */ } }, + .cpfd = NULL, }, { 0, /* No more characteristics in this service. */ }