feat(ble): Added ble examples for multiple connections

This commit is contained in:
Shen Weilong 2023-07-14 17:06:47 +08:00
parent a2d76ad38a
commit e9e6372690
35 changed files with 2018 additions and 7 deletions

View File

@ -340,7 +340,7 @@ config BT_LE_LL_SCA
config BT_LE_MAX_CONNECTIONS config BT_LE_MAX_CONNECTIONS
int "Maximum number of concurrent connections" int "Maximum number of concurrent connections"
depends on !BT_NIMBLE_ENABLED depends on !BT_NIMBLE_ENABLED
range 1 9 range 1 70
default 3 default 3
help help
Defines maximum number of concurrent BLE connections. For ESP32, user Defines maximum number of concurrent BLE connections. For ESP32, user

View File

@ -340,7 +340,7 @@ config BT_LE_LL_SCA
config BT_LE_MAX_CONNECTIONS config BT_LE_MAX_CONNECTIONS
int "Maximum number of concurrent connections" int "Maximum number of concurrent connections"
depends on !BT_NIMBLE_ENABLED depends on !BT_NIMBLE_ENABLED
range 1 9 range 1 35
default 3 default 3
help help
Defines maximum number of concurrent BLE connections. For ESP32, user Defines maximum number of concurrent BLE connections. For ESP32, user

View File

@ -65,6 +65,8 @@ config BT_NIMBLE_LOG_LEVEL
config BT_NIMBLE_MAX_CONNECTIONS config BT_NIMBLE_MAX_CONNECTIONS
int "Maximum number of concurrent connections" int "Maximum number of concurrent connections"
range 1 2 if IDF_TARGET_ESP32C2 range 1 2 if IDF_TARGET_ESP32C2
range 1 70 if IDF_TARGET_ESP32C6
range 1 35 if IDF_TARGET_ESP32H2
range 1 9 range 1 9
default 2 if IDF_TARGET_ESP32C2 default 2 if IDF_TARGET_ESP32C2
default 3 default 3
@ -825,3 +827,13 @@ config BT_NIMBLE_VS_SUPPORT
help help
This option is used to enable support for sending Vendor Specific HCI commands and handling This option is used to enable support for sending Vendor Specific HCI commands and handling
Vendor Specific HCI Events. Vendor Specific HCI Events.
config BT_NIMBLE_OPTIMIZE_MULTI_CONN
bool "Enable the optimization of multi-connection"
depends on SOC_BLE_MULTI_CONN_OPTIMIZATION
select BT_NIMBLE_VS_SUPPORT
default n
help
This option enables the use of vendor-specific APIs for multi-connections, which can
greatly enhance the stability of coexistence between numerous central and peripheral
devices. It will prohibit the usage of standard APIs.

@ -1 +1 @@
Subproject commit ec23739a744aff94762d5013fc60d50275ca797a Subproject commit 859cddf1f9d987d8c31a8b27688714463ed76c99

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -1707,4 +1707,12 @@
#define MYNEWT_VAL_BLE_HCI_VS (0) #define MYNEWT_VAL_BLE_HCI_VS (0)
#endif #endif
#ifndef MYNEWT_VAL_OPTIMIZE_MULTI_CONN
#ifdef CONFIG_BT_NIMBLE_OPTIMIZE_MULTI_CONN
#define MYNEWT_VAL_OPTIMIZE_MULTI_CONN CONFIG_BT_NIMBLE_OPTIMIZE_MULTI_CONN
#else
#define MYNEWT_VAL_OPTIMIZE_MULTI_CONN (0)
#endif
#endif
#endif #endif

View File

@ -1311,6 +1311,10 @@ config SOC_BLUFI_SUPPORTED
bool bool
default y default y
config SOC_BLE_MULTI_CONN_OPTIMIZATION
bool
default y
config SOC_BLE_USE_WIFI_PWR_CLK_WORKAROUND config SOC_BLE_USE_WIFI_PWR_CLK_WORKAROUND
bool bool
default y default y

View File

@ -532,5 +532,6 @@
#define SOC_BLE_DEVICE_PRIVACY_SUPPORTED (1) /*!< Support BLE device privacy mode */ #define SOC_BLE_DEVICE_PRIVACY_SUPPORTED (1) /*!< Support BLE device privacy mode */
#define SOC_BLE_POWER_CONTROL_SUPPORTED (1) /*!< Support Bluetooth Power Control */ #define SOC_BLE_POWER_CONTROL_SUPPORTED (1) /*!< Support Bluetooth Power Control */
#define SOC_BLUFI_SUPPORTED (1) /*!< Support BLUFI */ #define SOC_BLUFI_SUPPORTED (1) /*!< Support BLUFI */
#define SOC_BLE_MULTI_CONN_OPTIMIZATION (1) /*!< Support multiple connections optimization */
#define SOC_BLE_USE_WIFI_PWR_CLK_WORKAROUND (1) #define SOC_BLE_USE_WIFI_PWR_CLK_WORKAROUND (1)

View File

@ -1214,3 +1214,7 @@ config SOC_BLE_DEVICE_PRIVACY_SUPPORTED
config SOC_BLE_POWER_CONTROL_SUPPORTED config SOC_BLE_POWER_CONTROL_SUPPORTED
bool bool
default y default y
config SOC_BLE_MULTI_CONN_OPTIMIZATION
bool
default y

View File

@ -500,3 +500,4 @@
#define SOC_BLE_50_SUPPORTED (1) /*!< Support Bluetooth 5.0 */ #define SOC_BLE_50_SUPPORTED (1) /*!< Support Bluetooth 5.0 */
#define SOC_BLE_DEVICE_PRIVACY_SUPPORTED (1) /*!< Support BLE device privacy mode */ #define SOC_BLE_DEVICE_PRIVACY_SUPPORTED (1) /*!< Support BLE device privacy mode */
#define SOC_BLE_POWER_CONTROL_SUPPORTED (1) /*!< Support Bluetooth Power Control */ #define SOC_BLE_POWER_CONTROL_SUPPORTED (1) /*!< Support Bluetooth Power Control */
#define SOC_BLE_MULTI_CONN_OPTIMIZATION (1) /*!< Support multiple connections optimization */

View File

@ -168,6 +168,16 @@ examples/bluetooth/nimble/ble_multi_adv:
temporary: true temporary: true
reason: The runner doesn't support yet reason: The runner doesn't support yet
examples/bluetooth/nimble/ble_multi_conn:
enable:
- if: IDF_TARGET in ["esp32c6", "esp32h2"]
temporary: true
reason: the other targets are not tested yet
disable_test:
- if: IDF_TARGET in ["esp32c6", "esp32h2"]
temporary: true
reason: The runner doesn't support yet
examples/bluetooth/nimble/ble_periodic_adv: examples/bluetooth/nimble/ble_periodic_adv:
enable: enable:
- if: IDF_TARGET in ["esp32c2", "esp32c3", "esp32c6" , "esp32s3", "esp32h2" ] - if: IDF_TARGET in ["esp32c2", "esp32c3", "esp32c6" , "esp32s3", "esp32h2" ]

View File

@ -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_central_utils)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(blecent)

View File

@ -0,0 +1,76 @@
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |
# BLE Multiple Connection Central Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
Please check the [tutorial](tutorial/Ble_Multiple_Connections_Central_Example_Walkthrough.md) for more information about this example.
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
### Hardware Required
* At least two development board with ESP32-C6/ESP32-H2 SoC (e.g., ESP32-C6-DevKitC, ESP32-H2-DevKitC, etc.)
* USB cable for Power supply and programming
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
### 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
This is the console output on successful connection:
```
controller lib commit: [5cacafa]
I (564) ESP_MULTI_CONN_EXT: BLE Host Task Started
I (564) main_task: Returned from app_main()
I (604) ESP_MULTI_CONN_EXT: Create connection. -> peer addr e0:e1:f5:6f:ec:9d
I (1234) ESP_MULTI_CONN_EXT: Connection established. Handle:1, Total:1
I (2754) ESP_MULTI_CONN_EXT: Create connection. -> peer addr ee:16:69:80:72:d5
I (3394) ESP_MULTI_CONN_EXT: Connection established. Handle:2, Total:2
I (4454) ESP_MULTI_CONN_EXT: Create connection. -> peer addr ef:1a:6e:d6:64:44
I (5094) ESP_MULTI_CONN_EXT: Connection established. Handle:3, Total:3
I (6144) ESP_MULTI_CONN_EXT: Create connection. -> peer addr cb:f4:5d:b2:c8:1d
I (6244) ESP_MULTI_CONN_EXT: Connection established. Handle:4, Total:4
I (6444) ESP_MULTI_CONN_EXT: Create connection. -> peer addr e8:08:e5:ad:61:f6
I (7394) ESP_MULTI_CONN_EXT: Connection established. Handle:5, Total:5
I (8504) ESP_MULTI_CONN_EXT: Create connection. -> peer addr c1:53:a8:6f:2a:b4
I (9124) ESP_MULTI_CONN_EXT: Connection established. Handle:6, Total:6
I (9274) ESP_MULTI_CONN_EXT: Create connection. -> peer addr dd:fb:5b:13:6a:20
I (9904) ESP_MULTI_CONN_EXT: Connection established. Handle:7, Total:7
I (10934) ESP_MULTI_CONN_EXT: Create connection. -> peer addr d5:71:9c:fe:4f:6e
I (11574) ESP_MULTI_CONN_EXT: Connection established. Handle:8, Total:8
I (12264) ESP_MULTI_CONN_EXT: Create connection. -> peer addr d9:56:91:21:d4:25
I (12884) ESP_MULTI_CONN_EXT: Connection established. Handle:9, Total:9
I (13084) ESP_MULTI_CONN_EXT: Create connection. -> peer addr f7:f9:b1:73:38:13
I (13704) ESP_MULTI_CONN_EXT: Connection established. Handle:10, Total:10
I (14724) ESP_MULTI_CONN_EXT: Create connection. -> peer addr e7:e5:94:d0:32:78
I (15354) ESP_MULTI_CONN_EXT: Connection established. Handle:11, Total:11
I (16404) ESP_MULTI_CONN_EXT: Create connection. -> peer addr fb:c6:f9:46:11:dc
I (17344) ESP_MULTI_CONN_EXT: Connection established. Handle:12, Total:12
I (18434) ESP_MULTI_CONN_EXT: Create connection. -> peer addr c0:e3:ef:80:e6:fd
I (19374) ESP_MULTI_CONN_EXT: Connection established. Handle:13, Total:13
I (20484) ESP_MULTI_CONN_EXT: Create connection. -> peer addr d8:9d:6d:b8:c9:40
I (21104) ESP_MULTI_CONN_EXT: Connection established. Handle:14, Total:14
I (30814) ESP_MULTI_CONN_EXT: Connected to central. Handle:15
I (39624) BLE_CENT_SVC: Characteristic write; conn_handle=15
I (39624) BLE_CENT_SVC: 12
```
## 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.

View File

@ -0,0 +1,5 @@
set(srcs "main.c"
"gatt_svr.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,4 @@
menu "Example Configuration"
endmenu

View File

@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef H_BLECENT_
#define H_BLECENT_
#include "esp_central.h"
#ifdef __cplusplus
extern "C" {
#endif
int gatt_svr_init(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,178 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#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_multi_conn_cent.h"
#define TAG "BLE_MUTLI_CONN_CENT_SVC"
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);
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);
static int
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static 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[])
{ {
.uuid = &gatt_svr_chr_uuid.u,
.access_cb = gatt_svc_access,
.flags = BLE_GATT_CHR_F_WRITE,
.val_handle = &gatt_svr_chr_val_handle,
}, {
0, /* No more characteristics in this service. */
}
},
},
{
0, /* No more services. */
},
};
static int
gatt_svc_send_to_peers(const struct peer *peer, void *arg)
{
int rc;
const struct peer_chr *chr;
struct os_mbuf *src_om = arg;
struct os_mbuf *dst_om = NULL;
chr = peer_chr_find_uuid(peer, (const ble_uuid_t *)&gatt_svr_svc_uuid,
(const ble_uuid_t *)&gatt_svr_chr_uuid);
if (!chr) {
ESP_LOGE(TAG, "Didn't find the characteristic, handle:%d", peer->conn_handle);
goto failed_to_send;
}
dst_om = ble_hs_mbuf_att_pkt();
if (!dst_om) {
ESP_LOGE(TAG, "No enough buffer, handle:%d", peer->conn_handle);
goto failed_to_send;
}
rc = os_mbuf_appendfrom(dst_om, src_om, 0, os_mbuf_len(src_om));
if (rc) {
ESP_LOGE(TAG, "Failed to copy data, handle:%d, rc:%d", peer->conn_handle, rc);
goto failed_to_send;
}
rc = ble_gattc_write(peer->conn_handle, chr->chr.val_handle, dst_om, NULL, NULL);
if (rc) {
ESP_LOGE(TAG, "Failed to write, handle:%d, rc:%d", peer->conn_handle, rc);
/* The dst_om has already been freed in ble_gattc_write. */
dst_om = NULL;
goto failed_to_send;
}
return 0;
failed_to_send:
if (dst_om) {
os_mbuf_free_chain(dst_om);
}
return -1;
}
static int
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
uint8_t data[10];
uint8_t len;
struct os_mbuf *om;
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_WRITE_CHR:
ESP_LOGI(TAG, "Characteristic write; conn_handle=%d", conn_handle);
if (attr_handle == gatt_svr_chr_val_handle) {
om = ctxt->om;
len = os_mbuf_len(om);
len = len < sizeof(data) ? len : sizeof(data);
assert(os_mbuf_copydata(om, 0, len, data) == 0);
ESP_LOG_BUFFER_HEX(TAG, data, len);
/* Send the received data to all of peers. */
peer_traverse_all(gatt_svc_send_to_peers, om);
return 0;
}
goto unknown;
default:
goto unknown;
}
unknown:
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:
ESP_LOGD(TAG, "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:
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);
break;
case BLE_GATT_REGISTER_OP_DSC:
ESP_LOGD(TAG, "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;
}
}
int
gatt_svr_init(void)
{
int rc;
ble_svc_gap_init();
ble_svc_gatt_init();
rc = ble_gatts_count_cfg(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
rc = ble_gatts_add_svcs(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
return 0;
}

View File

@ -0,0 +1,469 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#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 "services/gap/ble_svc_gap.h"
#include "ble_multi_conn_cent.h"
#define BLE_PEER_NAME "esp-multi-conn"
#define BLE_PEER_MAX_NUM (MYNEWT_VAL(BLE_MAX_CONNECTIONS) - 1)
#define BLE_PREF_EVT_LEN_MS (5)
#define BLE_PREF_CONN_ITVL_MS (BLE_PEER_MAX_NUM * BLE_PREF_EVT_LEN_MS)
static const char *TAG = "ESP_MULTI_CONN_CENT";
static const ble_uuid_t *remote_svc_uuid =
BLE_UUID128_DECLARE(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12,
0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59);
static uint8_t ext_adv_pattern_1[] = {
0x02, 0x01, 0x06,
0x14, 0X09, 'e', 's', 'p', '-', 'b', 'l', 'e', '-', 'r', 'o', 'l', 'e', '-', 'c', 'o', 'e', 'x', '-', 'e',
};
void ble_store_config_init(void);
static void ble_cent_advertise(void);
static void ble_cent_scan(void);
static void ble_cent_connect(void *disc);
static uint8_t s_ble_multi_conn_num = 0;
/**
* Called when service discovery of the specified peer has completed.
*/
static void
ble_cent_on_disc_complete(const struct peer *peer, int status, void *arg)
{
if (status != 0) {
/* Service discovery failed. Terminate the connection. */
ESP_LOGE(TAG, "Error: Service discovery failed; status=%d conn_handle=%d", status,
peer->conn_handle);
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return;
}
/* Service discovery has completed successfully. Now we have a complete
* list of services, characteristics, and descriptors that the peer
* supports.
*/
ESP_LOGD(TAG, "Service discovery complete; status=%d conn_handle=%d\n", status,
peer->conn_handle);
}
/**
* The nimble host executes this callback when a GAP event occurs. The application associates a GAP
* event callback with each connection that is established. This callback will be only used by the
* central.
*
* @param event The event being signalled.
* @param arg Application-specified argument; unused by
* blecent.
*
* @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
ble_cent_client_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_hs_adv_fields fields;
int rc;
switch (event->type) {
case BLE_GAP_EVENT_EXT_DISC:
rc = ble_hs_adv_parse_fields(&fields, event->ext_disc.data, event->ext_disc.length_data);
/* An advertisment report was received during GAP discovery. */
if ((rc == 0) && fields.name && (fields.name_len >= strlen(BLE_PEER_NAME)) &&
!strncmp((const char *)fields.name, BLE_PEER_NAME, strlen(BLE_PEER_NAME))) {
ble_cent_connect(&event->ext_disc);
}
return 0;
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) {
ESP_LOGI(TAG, "Connection established. Handle:%d, Total:%d", event->connect.conn_handle,
++s_ble_multi_conn_num);
/* Remember peer. */
rc = peer_add(event->connect.conn_handle);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to add peer; rc=%d\n", rc);
} else {
/* Perform service discovery */
rc = peer_disc_svc_by_uuid(event->connect.conn_handle, remote_svc_uuid,
ble_cent_on_disc_complete, NULL);
if(rc != 0) {
ESP_LOGE(TAG, "Failed to discover services; rc=%d\n", rc);
}
}
} else {
/* Connection attempt failed; resume scanning. */
ESP_LOGE(TAG, "Central: Connection failed; status=0x%x\n", event->connect.status);
}
ble_cent_scan();
return 0;
case BLE_GAP_EVENT_DISCONNECT:
/* Connection terminated. */
print_conn_desc(&event->disconnect.conn);
/* Forget about peer. */
peer_delete(event->disconnect.conn.conn_handle);
ESP_LOGI(TAG, "Central disconnected; Handle:%d, Reason=%d, Total:%d",
event->disconnect.conn.conn_handle, event->disconnect.reason,
--s_ble_multi_conn_num);
/* Resume scanning. */
ble_cent_scan();
return 0;
case BLE_GAP_EVENT_DISC_COMPLETE:
ESP_LOGI(TAG, "discovery complete; reason=%d\n", event->disc_complete.reason);
return 0;
#if MYNEWT_VAL(BLE_POWER_CONTROL)
case BLE_GAP_EVENT_TRANSMIT_POWER:
ESP_LOGD(TAG, "Transmit power event : status=%d conn_handle=%d reason=%d phy=%d "
"power_level=%x power_level_flag=%d delta=%d", event->transmit_power.status,
event->transmit_power.conn_handle, event->transmit_power.reason,
event->transmit_power.phy, event->transmit_power.transmit_power_level,
event->transmit_power.transmit_power_level_flag, event->transmit_power.delta);
return 0;
case BLE_GAP_EVENT_PATHLOSS_THRESHOLD:
ESP_LOGD(TAG, "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
default:
return 0;
}
}
/**
* The nimble host executes this callback when a GAP event occurs. The application associates a GAP
* event callback with each connection that is established. This callback will be only used by the
* peripheral.
*
* @param event The event being signalled.
* @param arg Application-specified argument; unused by
* blecent.
*
* @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
ble_cent_server_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
/* The connectable adv has been established. We will act as the peripheral. */
if (event->connect.status == 0) {
ESP_LOGI(TAG, "Peripheral connected to central. Handle:%d", event->connect.conn_handle);
} else {
ESP_LOGE(TAG, "Peripheral: Connection failed; status=0x%x\n", event->connect.status);
ble_cent_advertise();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGI(TAG, "Peripheral disconnected; Handle:%d, Reason=%d",
event->disconnect.conn.conn_handle, event->disconnect.reason);
ble_cent_advertise();
return 0;
#if MYNEWT_VAL(BLE_POWER_CONTROL)
case BLE_GAP_EVENT_TRANSMIT_POWER:
ESP_LOGD(TAG, "Transmit power event : status=%d conn_handle=%d reason=%d phy=%d "
"power_level=%x power_level_flag=%d delta=%d", event->transmit_power.status,
event->transmit_power.conn_handle, event->transmit_power.reason,
event->transmit_power.phy, event->transmit_power.transmit_power_level,
event->transmit_power.transmit_power_level_flag, event->transmit_power.delta);
return 0;
case BLE_GAP_EVENT_PATHLOSS_THRESHOLD:
ESP_LOGD(TAG, "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
default:
return 0;
}
}
/**
* Enables advertising with the following parameters:
* o General discoverable mode.
* o Undirected connectable mode.
*/
static void
ble_cent_advertise(void)
{
int rc;
struct ble_gap_ext_adv_params params;
struct os_mbuf *data;
uint8_t instance = 0;
/* First check if any instance is already active */
if(ble_gap_ext_adv_active(instance)) {
return;
}
memset (&params, 0, sizeof(params));
/* Enable connectable advertising */
params.connectable = 1;
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
params.primary_phy = BLE_HCI_LE_PHY_1M;
params.secondary_phy = BLE_HCI_LE_PHY_1M;
params.tx_power = 127;
params.sid = 1;
params.itvl_min = BLE_GAP_ADV_ITVL_MS(300);
params.itvl_max = BLE_GAP_ADV_ITVL_MS(300);
rc = ble_gap_ext_adv_configure(instance, &params, NULL,
ble_cent_server_gap_event, NULL);
assert(rc == 0);
/* Get mbuf for adv data */
data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
assert(data);
rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
assert(rc == 0);
rc = ble_gap_ext_adv_set_data(instance, data);
assert(rc == 0);
/* Start advertising */
rc = ble_gap_ext_adv_start(instance, 0, 0);
assert(rc == 0);
if (rc) {
ESP_LOGE(TAG, "Failed to enable advertisement; rc=%d\n", rc);
return;
}
}
/**
* Initiates the GAP general discovery procedure.
*/
static void
ble_cent_scan(void)
{
int rc;
if (ble_gap_disc_active()) {
return;
}
struct ble_gap_ext_disc_params uncoded_disc_params;
struct ble_gap_ext_disc_params coded_disc_params;
/* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
uncoded_disc_params.passive = 1;
uncoded_disc_params.itvl = BLE_GAP_SCAN_ITVL_MS(500);
uncoded_disc_params.window = BLE_GAP_SCAN_WIN_MS(200);
coded_disc_params.passive = 1;
coded_disc_params.itvl = BLE_GAP_SCAN_ITVL_MS(500);
coded_disc_params.window = BLE_GAP_SCAN_WIN_MS(300);
/* 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, &uncoded_disc_params,
&coded_disc_params, ble_cent_client_gap_event, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "Error initiating GAP discovery procedure; rc=%d\n", rc);
}
}
/**
* Connects to the sender of the specified advertisement.The advertisement must contain its full
* name which we will compare with 'BLE_PEER_NAME'.
*/
static void
ble_cent_connect(void *disc)
{
ble_addr_t own_addr;
ble_addr_t *peer_addr;
struct ble_gap_multi_conn_params multi_conn_params;
struct ble_gap_conn_params uncoded_conn_param;
struct ble_gap_conn_params coded_conn_param;
int rc;
if (s_ble_multi_conn_num >= BLE_PEER_MAX_NUM) {
return;
}
/* Scanning must be stopped before a connection can be initiated. */
rc = ble_gap_disc_cancel();
if (rc != 0) {
ESP_LOGE(TAG, "Failed to cancel scan; rc=%d\n", rc);
return;
}
/* We won't connect to the same device. Change our static random address to simulate
* multi-connection with only one central and one peripheral.
*/
rc = ble_hs_id_gen_rnd(0, &own_addr);
assert(rc == 0);
rc = ble_hs_id_set_rnd(own_addr.val);
assert(rc == 0);
peer_addr = &((struct ble_gap_ext_disc_desc *)disc)->addr;
/* The connection and scan parameters for uncoded phy (1M & 2M). */
uncoded_conn_param.scan_itvl = BLE_GAP_SCAN_ITVL_MS(300);
uncoded_conn_param.scan_window = BLE_GAP_SCAN_WIN_MS(100);
uncoded_conn_param.itvl_min = BLE_GAP_CONN_ITVL_MS(BLE_PREF_CONN_ITVL_MS);
uncoded_conn_param.itvl_max = BLE_GAP_CONN_ITVL_MS(BLE_PREF_CONN_ITVL_MS);
uncoded_conn_param.latency = 0;
uncoded_conn_param.supervision_timeout = BLE_GAP_SUPERVISION_TIMEOUT_MS(BLE_PREF_CONN_ITVL_MS * 30);
uncoded_conn_param.min_ce_len = 0;
uncoded_conn_param.max_ce_len = BLE_GAP_CONN_ITVL_MS(BLE_PREF_CONN_ITVL_MS);
/* The connection and scan parameters for coded phy (125k & 500k) */
coded_conn_param.scan_itvl = BLE_GAP_SCAN_ITVL_MS(300);
coded_conn_param.scan_window = BLE_GAP_SCAN_WIN_MS(200);
coded_conn_param.itvl_min = BLE_GAP_CONN_ITVL_MS(BLE_PREF_CONN_ITVL_MS);
coded_conn_param.itvl_max = BLE_GAP_CONN_ITVL_MS(BLE_PREF_CONN_ITVL_MS);
coded_conn_param.latency = 0;
coded_conn_param.supervision_timeout = BLE_GAP_SUPERVISION_TIMEOUT_MS(BLE_PREF_CONN_ITVL_MS * 30);
coded_conn_param.min_ce_len = 0;
coded_conn_param.max_ce_len = BLE_GAP_CONN_ITVL_MS(BLE_PREF_CONN_ITVL_MS);
/* The parameters for multi-connect. We expect that this connection has at least
* BLE_PREF_EVT_LEN_MS every interval to Rx and Tx.
*/
multi_conn_params.scheduling_len_us = BLE_PREF_EVT_LEN_MS * 1000;
multi_conn_params.own_addr_type = BLE_OWN_ADDR_RANDOM;
multi_conn_params.peer_addr = peer_addr;
multi_conn_params.duration_ms = 8000;
multi_conn_params.phy_mask = BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK |
BLE_GAP_LE_PHY_CODED_MASK;
multi_conn_params.phy_1m_conn_params = &uncoded_conn_param;
multi_conn_params.phy_2m_conn_params = &uncoded_conn_param;
multi_conn_params.phy_coded_conn_params = &coded_conn_param;
rc = ble_gap_multi_connect(&multi_conn_params, ble_cent_client_gap_event, NULL);
if (rc) {
ESP_LOGE(TAG, "Error: Failed to connect to device; addr_type=%d addr=%s; rc=%d\n",
peer_addr->type, addr_str(peer_addr->val), rc);
} else {
ESP_LOGI(TAG, "Create connection. -> peer addr %s", addr_str(peer_addr->val));
}
}
static void
blecent_on_reset(int reason)
{
ESP_LOGE(TAG, "Resetting state; reason=%d\n", reason);
}
static void
blecent_on_sync(void)
{
int rc;
/*
* To improve both throughput and stability, it is recommended to set the connection interval
* as an integer multiple of the `MINIMUM_CONN_INTERVAL`. This `MINIMUM_CONN_INTERVAL` should
* be calculated based on the total number of connections and the Transmitter/Receiver phy.
*
* Note that the `MINIMUM_CONN_INTERVAL` value should meet the condition that:
* MINIMUM_CONN_INTERVAL > ((MAX_TIME_OF_PDU * 2) + 150us) * CONN_NUM.
*
* For example, if we have 10 connections, maxmum TX/RX length is 251 and the phy is 1M, then
* the `MINIMUM_CONN_INTERVAL` should be greater than ((261 * 8us) * 2 + 150us) * 10 = 43260us.
*
*/
rc = ble_gap_common_factor_set(true, (BLE_PREF_CONN_ITVL_MS * 1000) / 625);
assert(rc == 0);
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* We will function as both the central and peripheral device, connecting to all peripherals
* with the name of BLE_PEER_NAME. Meanwhile, a connectable advertising will be enabled.
* In this example, we register two gap callback functions.
* - ble_cent_client_gap_event: Used by the central.
* - ble_cent_server_gap_event: Used by the peripheral.
*/
ble_cent_advertise();
ble_cent_scan();
}
void blecent_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;
}
/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
rc = peer_init(BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM);
assert(rc == 0);
/* Set the default device name. We will act as both central and peripheral. */
rc = ble_svc_gap_device_name_set("esp-ble-role-coex");
assert(rc == 0);
rc = gatt_svr_init();
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(blecent_host_task);
}

View File

@ -0,0 +1,13 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=70
CONFIG_BT_NIMBLE_GATT_MAX_PROCS=70
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=100
CONFIG_BT_NIMBLE_OPTIMIZE_MULTI_CONN=y
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
CONFIG_BT_NIMBLE_BLE_POWER_CONTROL=y

View File

@ -0,0 +1,6 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=35
CONFIG_BT_NIMBLE_GATT_MAX_PROCS=35
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50

View File

@ -0,0 +1,242 @@
# BLE Multiple Connections Central Example Walkthrough
## Introduction
In this tutorial, the multiple connection example code for the espressif chipsets with BLE5.0 support is reviewed. The example demonstrates a scenario where dozens of connections are working simultaneously to showcase how to invoke vendor APIs to establish connections and enhance connection stability. While acting as a central device to connect multiple peripherals, it also broadcasts itself as connectable and can be connected by a phone. Once the phone successfully connects, it can perform write operations on all connected devices.
To minimize the number of development boards, the central and peripheral devices can simulate multiple connections by changing their static random addresses. Therefore, this example only requires two Espressif development boards. Multiple development boards can also be used to simulate a real-world usage scenario.
## Includes
This example is located in the examples folder of the ESP-IDF under the [ble_multi_conn_cent/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 <string.h>
#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 "services/gap/ble_svc_gap.h"
#include "ble_multi_conn_cent.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”`, `“ble_multi_conn_cent.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_multi_conn_cent.h`: Defines the functions used for multiple connections.
## Main Entry Point
The programs 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;
}
/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
rc = peer_init(BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM, BLE_PEER_MAX_NUM);
assert(rc == 0);
/* Set the default device name. We will act as both central and peripheral. */
rc = ble_svc_gap_device_name_set("esp-ble-role-coex");
assert(rc == 0);
rc = gatt_svr_init();
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(blecent_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.
```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. 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 = ble_multi_adv_on_reset;
ble_hs_cfg.sync_cb = ble_multi_adv_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 calls `ble_svc_gap_device_name_set()` to set the default device name. 'esp-ble-role-coex' is passed as the default device name to this function.
```c
rc = ble_svc_gap_device_name_set("esp-ble-role-coex");
```
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(ble_multi_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 since something needs to handle the default queue, it is easier to create a separate task.
## Multiple Connections
This example will be executed according to the following steps:
* Call the vendor APIs to set a common factor. All subsequent connections need to set the interval as a multiple of this number to reduce mutual interference between different connections.
```c
/*
* To improve both throughput and stability, it is recommended to set the connection interval
* as an integer multiple of the `MINIMUM_CONN_INTERVAL`. This `MINIMUM_CONN_INTERVAL` should
* be calculated based on the total number of connections and the Transmitter/Receiver phy.
*
* Note that the `MINIMUM_CONN_INTERVAL` value should meet the condition that:
* MINIMUM_CONN_INTERVAL > ((MAX_TIME_OF_PDU * 2) + 150us) * CONN_NUM.
*
* For example, if we have 10 connections, maxmum TX/RX length is 251 and the phy is 1M, then
* the `MINIMUM_CONN_INTERVAL` should be greater than ((261 * 8us) * 2 + 150us) * 10 = 43260us.
*
*/
rc = ble_gap_common_factor_set(true, (BLE_PREF_CONN_ITVL_MS * 1000) / 625);
```
* Enable both scan and adv simultaneously.
```c
/* We will function as both the central and peripheral device, connecting to all peripherals
* with the name of BLE_PEER_NAME. Meanwhile, a connectable advertising will be enabled.
* In this example, we register two gap callback functions.
* - ble_cent_client_gap_event: Used by the central.
* - ble_cent_server_gap_event: Used by the peripheral.
*/
ble_cent_advertise();
ble_cent_scan();
```
* In the callback function of the scan, compare the name from the received adv. If it is the device you want to connect to, initiate the connection with the specified connection parameters.
> When sending multiple connect requests to the same device, the peer may reject duplicate connections. Therefore, before initiating a connection, it is necessary to change your own static random address.
```c
/* The parameters for multi-connect. We expect that this connection has at least
* BLE_PREF_EVT_LEN_MS every interval to Rx and Tx.
*/
multi_conn_params.scheduling_len_us = BLE_PREF_EVT_LEN_MS * 1000;
multi_conn_params.own_addr_type = BLE_OWN_ADDR_RANDOM;
multi_conn_params.peer_addr = peer_addr;
multi_conn_params.duration_ms = 3000;
multi_conn_params.phy_mask = BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK |
BLE_GAP_LE_PHY_CODED_MASK;
multi_conn_params.phy_1m_conn_params = &uncoded_conn_param;
multi_conn_params.phy_2m_conn_params = &uncoded_conn_param;
multi_conn_params.phy_coded_conn_params = &coded_conn_param;
rc = ble_gap_multi_connect(&multi_conn_params, ble_cent_client_gap_event, NULL);
```
* The connection will be automatically established. When the maximum number of connections is reached, you can connect to a device named "esp-ble-role-coex-e" using your mobile phone (with `nRF Connect` APP). After writing to its characteristic , the central device will forward the received data to all peripherals.
![The screenshot of the APP](./phone_screenshot.png)
## Conclusion
Users can use this example to understand how to use the vendor APIs and experience the stability of multiple connections it brings.

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -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(bleprph)

View File

@ -0,0 +1,114 @@
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |
# BLE Multiple Connection Peripheral Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
Please check the [tutorial](tutorial/Ble_Multiple_Connections_Peripheral_Example_Walkthrough.md) for more information about this example.
## How to Use Example
Before project configuration and build, be sure to set the correct chip target using:
```bash
idf.py set-target <chip_name>
```
### Hardware Required
* At least two development board with ESP32-C6/ESP32-H2 SoC (e.g., ESP32-C6-DevKitC, ESP32-H2-DevKitC, etc.)
* USB cable for Power supply and programming
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
### 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
This is the console output on successful connection:
```
controller lib commit: [5cacafa]
I (444) ESP_MULTI_CONN_EXT: BLE Host Task Started
I (1854) ESP_MULTI_CONN_EXT: Started adv, Device Address e0:e1:f5:6f:ec:9d
I (6254) ESP_MULTI_CONN_EXT: Connection established. Handle:1. Total:1
I (6254) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (6504) ESP_MULTI_CONN_EXT: Started adv, Device Address ee:16:69:80:72:d5
I (8414) ESP_MULTI_CONN_EXT: Connection established. Handle:2. Total:2
I (8414) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (8654) ESP_MULTI_CONN_EXT: Started adv, Device Address ef:1a:6e:d6:64:44
I (10124) ESP_MULTI_CONN_EXT: Connection established. Handle:3. Total:3
I (10124) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (10434) ESP_MULTI_CONN_EXT: Started adv, Device Address cb:f4:5d:b2:c8:1d
I (11264) ESP_MULTI_CONN_EXT: Connection established. Handle:4. Total:4
I (11264) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (11464) ESP_MULTI_CONN_EXT: Started adv, Device Address e8:08:e5:ad:61:f6
I (12414) ESP_MULTI_CONN_EXT: Connection established. Handle:5. Total:5
I (12414) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (12794) ESP_MULTI_CONN_EXT: Started adv, Device Address c1:53:a8:6f:2a:b4
I (14154) ESP_MULTI_CONN_EXT: Connection established. Handle:6. Total:6
I (14154) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (14294) ESP_MULTI_CONN_EXT: Started adv, Device Address dd:fb:5b:13:6a:20
I (14934) ESP_MULTI_CONN_EXT: Connection established. Handle:7. Total:7
I (14934) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (15324) ESP_MULTI_CONN_EXT: Started adv, Device Address d5:71:9c:fe:4f:6e
I (16594) ESP_MULTI_CONN_EXT: Connection established. Handle:8. Total:8
I (16594) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (16974) ESP_MULTI_CONN_EXT: Started adv, Device Address d9:56:91:21:d4:25
I (17904) ESP_MULTI_CONN_EXT: Connection established. Handle:9. Total:9
I (17914) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (18104) ESP_MULTI_CONN_EXT: Started adv, Device Address f7:f9:b1:73:38:13
I (18734) ESP_MULTI_CONN_EXT: Connection established. Handle:10. Total:10
I (18734) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (19004) ESP_MULTI_CONN_EXT: Started adv, Device Address e7:e5:94:d0:32:78
I (20374) ESP_MULTI_CONN_EXT: Connection established. Handle:11. Total:11
I (20374) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (20684) ESP_MULTI_CONN_EXT: Started adv, Device Address fb:c6:f9:46:11:dc
I (22374) ESP_MULTI_CONN_EXT: Connection established. Handle:12. Total:12
I (22374) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (22594) ESP_MULTI_CONN_EXT: Started adv, Device Address c0:e3:ef:80:e6:fd
I (24394) ESP_MULTI_CONN_EXT: Connection established. Handle:13. Total:13
I (24404) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (24674) ESP_MULTI_CONN_EXT: Started adv, Device Address d8:9d:6d:b8:c9:40
I (26134) ESP_MULTI_CONN_EXT: Connection established. Handle:14. Total:14
I (26134) ESP_MULTI_CONN_EXT: advertisement completed. Reason=0.
I (44654) BLE_PRPH_SVC: Characteristic write; conn_handle=1
I (44654) BLE_PRPH_SVC: 12
I (44674) BLE_PRPH_SVC: Characteristic write; conn_handle=2
I (44674) BLE_PRPH_SVC: 12
I (44684) BLE_PRPH_SVC: Characteristic write; conn_handle=3
I (44684) BLE_PRPH_SVC: 12
I (44694) BLE_PRPH_SVC: Characteristic write; conn_handle=4
I (44694) BLE_PRPH_SVC: 12
I (44724) BLE_PRPH_SVC: Characteristic write; conn_handle=6
I (44724) BLE_PRPH_SVC: 12
I (44734) BLE_PRPH_SVC: Characteristic write; conn_handle=7
I (44734) BLE_PRPH_SVC: 12
I (44754) BLE_PRPH_SVC: Characteristic write; conn_handle=8
I (44754) BLE_PRPH_SVC: 12
I (44764) BLE_PRPH_SVC: Characteristic write; conn_handle=9
I (44764) BLE_PRPH_SVC: 12
I (44784) BLE_PRPH_SVC: Characteristic write; conn_handle=10
I (44784) BLE_PRPH_SVC: 12
I (44804) BLE_PRPH_SVC: Characteristic write; conn_handle=12
I (44804) BLE_PRPH_SVC: 12
I (44814) BLE_PRPH_SVC: Characteristic write; conn_handle=13
I (44814) BLE_PRPH_SVC: 12
I (44834) BLE_PRPH_SVC: Characteristic write; conn_handle=14
I (44834) BLE_PRPH_SVC: 12
I (44914) BLE_PRPH_SVC: Characteristic write; conn_handle=5
I (44914) BLE_PRPH_SVC: 12
I (45194) BLE_PRPH_SVC: Characteristic write; conn_handle=11
I (45194) BLE_PRPH_SVC: 12
```
## 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.

View File

@ -0,0 +1,5 @@
set(srcs "main.c"
"gatt_svr.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,20 @@
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
config EXAMPLE_RESTART_ADV_AFTER_CONNECTED
bool
default y
prompt "Restart advertisement when connected"
help
To simulate multiple connections with only one device,
restart the advertisement once a connection has been established.
endmenu

View File

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef H_BLEPRPH_
#define H_BLEPRPH_
#include <stdbool.h>
#include "nimble/ble.h"
#include "modlog/modlog.h"
#include "esp_peripheral.h"
#ifdef __cplusplus
extern "C" {
#endif
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
int gatt_svr_init(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,131 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <stdio.h>
#include "esp_log.h"
#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_multi_conn_prph.h"
#define TAG "BLE_MUTLI_CONN_PRPH_SVC"
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 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);
static int
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static 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[])
{ {
.uuid = &gatt_svr_chr_uuid.u,
.access_cb = gatt_svc_access,
.flags = BLE_GATT_CHR_F_WRITE,
.val_handle = &gatt_svr_chr_val_handle,
}, {
0, /* No more characteristics in this service. */
}
},
},
{
0, /* No more services. */
},
};
static int
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
uint8_t data[10];
uint8_t len;
struct os_mbuf *om;
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_WRITE_CHR:
ESP_LOGI(TAG, "Characteristic write; conn_handle=%d", conn_handle);
if (attr_handle == gatt_svr_chr_val_handle) {
om = ctxt->om;
len = os_mbuf_len(om);
len = len < sizeof(data) ? len : sizeof(data);
assert(os_mbuf_copydata(om, 0, len, data) == 0);
ESP_LOG_BUFFER_HEX(TAG, data, len);
return 0;
}
goto unknown;
default:
goto unknown;
}
unknown:
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:
ESP_LOGD(TAG, "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:
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);
break;
case BLE_GATT_REGISTER_OP_DSC:
ESP_LOGD(TAG, "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;
}
}
int
gatt_svr_init(void)
{
int rc;
ble_svc_gap_init();
ble_svc_gatt_init();
rc = ble_gatts_count_cfg(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
rc = ble_gatts_add_svcs(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
return 0;
}

View File

@ -0,0 +1,323 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_random.h"
/* BLE */
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "ble_multi_conn_prph.h"
#if CONFIG_EXAMPLE_EXTENDED_ADV
static uint8_t ext_adv_pattern_1[] = {
0x02, 0x01, 0x06,
0x03, 0x03, 0xab, 0xcd,
0x03, 0x03, 0x18, 0x11,
0x11, 0X09, 'e', 's', 'p', '-', 'm', 'u', 'l', 't', 'i', '-', 'c', 'o', 'n', 'n', '-', 'e',
};
#endif
static const char *TAG = "ESP_MULTI_CONN_PRPH";
static uint8_t s_ble_prph_conn_num = 0;
static SemaphoreHandle_t s_sem_restart_adv = NULL;
static int ble_prph_gap_event(struct ble_gap_event *event, void *arg);
void ble_store_config_init(void);
/**
* Enables advertising with the following parameters:
* o General discoverable mode.
* o Undirected connectable mode.
*/
static void
ble_prph_advertise(void)
{
int rc;
ble_addr_t addr;
if (s_ble_prph_conn_num >= CONFIG_BT_NIMBLE_MAX_CONNECTIONS) {
return;
}
#if CONFIG_EXAMPLE_EXTENDED_ADV
struct ble_gap_ext_adv_params params;
struct os_mbuf *data;
uint8_t instance = 0;
/* First check if any instance is already active */
if(ble_gap_ext_adv_active(instance)) {
return;
}
memset (&params, 0, sizeof(params));
/* enable connectable advertising */
params.connectable = 1;
/* 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.tx_power = 127;
params.sid = 1;
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, &params, NULL,
ble_prph_gap_event, NULL);
assert(rc == 0);
data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
assert(data);
rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
assert(rc == 0);
rc = ble_gap_ext_adv_set_data(instance, data);
assert(rc == 0);
/* We won't connect to a connected device. Change our static random address to simulate
* multi-connection with only one central and one peripheral.
*/
rc = ble_hs_id_gen_rnd(0, &addr);
assert(rc == 0);
rc = ble_gap_ext_adv_set_addr(instance, &addr);
assert(rc == 0);
/* start advertising */
rc = ble_gap_ext_adv_start(instance, 0, 0);
assert(rc == 0);
#else
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
const char *name;
if (ble_gap_adv_active()) {
return;
}
/* We won't connect to a connected device. Change our static random address to simulate
* multi-connection with only one central and one peripheral.
*/
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);
/**
* Set the advertisement data included in our advertisements:
* o Flags (indicates advertisement type and other general info).
* o Advertising tx power.
* o Device name.
*/
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;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
ESP_LOGE(TAG, "failed to set 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(BLE_OWN_ADDR_RANDOM, NULL, BLE_HS_FOREVER,
&adv_params, ble_prph_gap_event, NULL);
#endif // CONFIG_EXAMPLE_EXTENDED_ADV
if (rc) {
ESP_LOGE(TAG, "Failed to enable advertisement; rc=%d\n", rc);
return;
} else {
ESP_LOGI(TAG, "Started adv, Device Address %s", addr_str(addr.val));
}
}
static void
ble_prph_restart_adv(void)
{
#if CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
if (!xSemaphoreGive(s_sem_restart_adv)) {
ESP_LOGE(TAG, "Failed to give Semaphor");
}
#else
ble_prph_advertise();
#endif // CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
}
/**
* The nimble host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that forms.
* bleprph 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
* bleprph.
*
* @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
ble_prph_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) {
/* A new connection was established. */
ESP_LOGI(TAG, "Connection established. Handle:%d. Total:%d", event->connect.conn_handle,
++s_ble_prph_conn_num);
#if !CONFIG_EXAMPLE_EXTENDED_ADV && CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
ble_prph_restart_adv();
#endif // !CONFIG_EXAMPLE_EXTENDED_ADV && CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
} else {
/* Restart the advertising */
ble_prph_restart_adv();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGI(TAG, "Disconnect. Handle:%d. Reason=%d. Total:%d",
event->disconnect.conn.conn_handle, event->disconnect.reason, --s_ble_prph_conn_num);
/* Connection terminated; resume advertising. */
ble_prph_restart_adv();
return 0;
#if CONFIG_EXAMPLE_EXTENDED_ADV
case BLE_GAP_EVENT_ADV_COMPLETE:
ESP_LOGI(TAG, "advertisement completed. Reason=%d.",event->adv_complete.reason);
#if CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
ble_prph_restart_adv();
#endif // CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
return 0;
#endif // CONFIG_EXAMPLE_EXTENDED_ADV
#if MYNEWT_VAL(BLE_POWER_CONTROL)
case BLE_GAP_EVENT_TRANSMIT_POWER:
ESP_LOGD(TAG, "Transmit power event : status=%d conn_handle=%d reason=%d "
"phy=%d power_level=%x power_level_flag=%d delta=%d",
event->transmit_power.status, event->transmit_power.conn_handle,
event->transmit_power.reason, event->transmit_power.phy,
event->transmit_power.transmit_power_level,
event->transmit_power.transmit_power_level_flag, event->transmit_power.delta);
return 0;
case BLE_GAP_EVENT_PATHLOSS_THRESHOLD:
ESP_LOGD(TAG, "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
}
return 0;
}
static void
bleprph_on_reset(int reason)
{
ESP_LOGE(TAG, "Resetting state; reason=%d\n", reason);
}
static void
bleprph_on_sync(void)
{
/* Begin advertising. */
ble_prph_advertise();
}
void bleprph_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);
s_sem_restart_adv = xSemaphoreCreateBinary();
assert(s_sem_restart_adv);
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 = bleprph_on_reset;
ble_hs_cfg.sync_cb = bleprph_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("esp-multi-conn");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(bleprph_host_task);
#if CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
int delay_ms;
/* Restart the advertising if the connection has been established successfully. This can
* help to simulate multiple devices with only one peripheral development board.
*/
while (true)
{
if (xSemaphoreTake(s_sem_restart_adv, portMAX_DELAY)) {
/* Delay a random time to increase the randomness of the test. */
delay_ms = (esp_random() % 300) + 100;
vTaskDelay(pdMS_TO_TICKS(delay_ms));
ble_prph_advertise();
} else {
ESP_LOGE(TAG, "Failed to take Semaphor");
}
}
#endif // CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
}

View File

@ -0,0 +1,11 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70
CONFIG_BT_NIMBLE_EXT_ADV=y
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=69
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=100
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
CONFIG_BT_NIMBLE_BLE_POWER_CONTROL=y

View File

@ -0,0 +1,5 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=34
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=50

View File

@ -0,0 +1,234 @@
# BLE Multiple Connections Peripheral Example Walkthrough
## Introduction
In this tutorial, the multiple connection example code for the espressif chipsets with BLE5.0 support is reviewed. The example demonstrates a scenario where dozens of connections are working simultaneously to showcase how to invoke vendor APIs to establish connections and enhance connection stability. While acting as a central device to connect multiple peripherals, it also broadcasts itself as connectable and can be connected by a phone. Once the phone successfully connects, it can perform write operations on all connected devices.
To minimize the number of development boards, the central and peripheral devices can simulate multiple connections by changing their static random addresses. Therefore, this example only requires two Espressif development boards. Multiple development boards can also be used to simulate a real-world usage scenario.
## Includes
This example is located in the examples folder of the ESP-IDF under the [ble_multi_conn_prph/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"
#include "esp_random.h"
/* BLE */
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "ble_multi_conn_prph.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”`, `“ble_multi_conn_prph.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_multi_conn_prph.h`: Defines the functions used for multiple connections.
## Main Entry Point
The programs 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);
s_sem_restart_adv = xSemaphoreCreateBinary();
assert(s_sem_restart_adv);
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 = bleprph_on_reset;
ble_hs_cfg.sync_cb = bleprph_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("esp-multi-conn");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(bleprph_host_task);
#if CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
int delay_ms;
/* Restart the advertising if the connection has been established successfully. This can
* help to simulate multiple devices with only one peripheral development board.
*/
while (true)
{
if (xSemaphoreTake(s_sem_restart_adv, portMAX_DELAY)) {
/* Delay a random time to increase the randomness of the test. */
delay_ms = (esp_random() % 300) + 100;
vTaskDelay(pdMS_TO_TICKS(delay_ms));
ble_prph_advertise();
} else {
ESP_LOGE(TAG, "Failed to take Semaphor");
}
}
#endif // CONFIG_EXAMPLE_RESTART_ADV_AFTER_CONNECTED
}
```
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.
```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. 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 = ble_multi_adv_on_reset;
ble_hs_cfg.sync_cb = ble_multi_adv_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 calls `ble_svc_gap_device_name_set()` to set the default device name. 'esp-multi-conn' is passed as the default device name to this function.
```c
rc = ble_svc_gap_device_name_set("esp-multi-conn");
```
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(ble_multi_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 since something needs to handle the default queue, it is easier to create a separate task.
## Multiple Connections
This example will be executed according to the following steps:
* Set the advertising data (Adv data) and enable connectable advertising
> Set the “name” field in the Adv data as “esp-multi-conn” to allow the counterpart devices to recognize and initiate a connection.
* After a successful connection, call "ble_prph_gap_event" and send a semaphore to the main task.
* To simulate a multi-connection scenario using a development board, "app_main" function is modified to receive a semaphore and perform a random delay. This approach will increase the randomness of the example and better simulate real-world usage scenarios.
You can enable or disable this functionality through the "EXAMPLE_RESTART_ADV_AFTER_CONNECTED" in the menuconfig. Additionally, using multiple development boards will further enhance the simulation of real-world usage scenarios.
```c
int delay_ms;
/* Restart the advertising if the connection has been established successfully. This can
* help to simulate multiple devices with only one peripheral development board.
*/
while (true)
{
if (xSemaphoreTake(s_sem_restart_adv, portMAX_DELAY)) {
/* Delay a random time to increase the randomness of the test. */
delay_ms = (esp_random() % 300) + 100;
vTaskDelay(pdMS_TO_TICKS(delay_ms));
ble_prph_advertise();
} else {
ESP_LOGE(TAG, "Failed to take Semaphor");
}
}
```
* Connections will be created automatically. When the maximum number of connections is reached, sending connectable advertisements will be stopped.
* You can use a mobile phone to connect to the central device and perform write operations on all local connections.
> For more details, please check [Central tutorial](../../ble_multi_conn_cent/tutorial/Ble_Multiple_Connections_Central_Example_Walkthrough.md)
## Conclusion
Users can use this example to understand how to use the vendor APIs and experience the stability of multiple connections it brings.

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@ -48,6 +48,16 @@ SLIST_HEAD(peer_svc_list, peer_svc);
struct peer; struct peer;
typedef void peer_disc_fn(const struct peer *peer, int status, void *arg); typedef void peer_disc_fn(const struct peer *peer, int status, void *arg);
/**
* @brief The callback function for the devices traversal.
*
* @param peer
* @param arg
* @return int 0, continue; Others, stop the traversal.
*
*/
typedef int peer_traverse_fn(const struct peer *peer, void *arg);
struct peer { struct peer {
SLIST_ENTRY(peer) next; SLIST_ENTRY(peer) next;
@ -65,6 +75,11 @@ struct peer {
void *disc_cb_arg; void *disc_cb_arg;
}; };
void peer_traverse_all(peer_traverse_fn *trav_cb, void *arg);
int peer_disc_svc_by_uuid(uint16_t conn_handle, const ble_uuid_t *uuid, peer_disc_fn *disc_cb,
void *disc_cb_arg);
int peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, int peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb,
void *disc_cb_arg); void *disc_cb_arg);
const struct peer_dsc * const struct peer_dsc *

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@ -619,6 +619,36 @@ peer_svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
return rc; return rc;
} }
int
peer_disc_svc_by_uuid(uint16_t conn_handle, const ble_uuid_t *uuid, peer_disc_fn *disc_cb,
void *disc_cb_arg)
{
struct peer_svc *svc;
struct peer *peer;
int rc;
peer = peer_find(conn_handle);
if (peer == NULL) {
return BLE_HS_ENOTCONN;
}
/* Undiscover everything first. */
while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) {
SLIST_REMOVE_HEAD(&peer->svcs, next);
peer_svc_delete(svc);
}
peer->disc_prev_chr_val = 1;
peer->disc_cb = disc_cb;
peer->disc_cb_arg = disc_cb_arg;
rc = ble_gattc_disc_svc_by_uuid(conn_handle, uuid, peer_svc_disced, peer);
if (rc != 0) {
return rc;
}
return 0;
}
int int
peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, void *disc_cb_arg) peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, void *disc_cb_arg)
@ -702,6 +732,22 @@ peer_add(uint16_t conn_handle)
return 0; return 0;
} }
void
peer_traverse_all(peer_traverse_fn *trav_cb, void *arg)
{
struct peer *peer;
if (!trav_cb) {
return;
}
SLIST_FOREACH(peer, &peers, next) {
if (trav_cb(peer, arg)) {
return;
}
}
}
static void static void
peer_free_mem(void) peer_free_mem(void)
{ {

View File

@ -21,6 +21,7 @@ int scli_receive_key(int *key);
/** Misc. */ /** Misc. */
void print_bytes(const uint8_t *bytes, int len); void print_bytes(const uint8_t *bytes, int len);
void print_addr(const void *addr); void print_addr(const void *addr);
char *addr_str(const void *addr);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@ -28,3 +28,16 @@ print_addr(const void *addr)
MODLOG_DFLT(INFO, "%02x:%02x:%02x:%02x:%02x:%02x", MODLOG_DFLT(INFO, "%02x:%02x:%02x:%02x:%02x:%02x",
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]); u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
} }
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;
}